Xgboost: [Новая функция] Монотонные ограничения в построении дерева

Созданный на 27 авг. 2016  ·  46Комментарии  ·  Источник: dmlc/xgboost

Я получил несколько запросов о поддержке монотонных ограничений для определенной функции в отношении вывода,

т. е. когда другие функции фиксированы, сделать прогноз монотонно увеличивающимся по отношению к определенной указанной функции. Я открываю этот выпуск, чтобы увидеть общий интерес к этой функции. Я могу добавить это, если это будет достаточно интересно,

Мне понадобится помощь добровольцев из сообщества, чтобы протестировать бета-функцию и предоставить документ и руководство по использованию этой функции. Пожалуйста, ответьте на вопрос, если вам интересно

Самый полезный комментарий

В настоящее время этой функции нет в API Sklearn. Не могли бы вы или кто-нибудь помочь добавить его? Спасибо!

Все 46 Комментарий

Экспериментальная версия представлена ​​на https://github.com/dmlc/xgboost/pull/1516. Чтобы использовать это до объединения, клонируйте репо https://github.com/tqchen/xgboost ,

Включите следующие параметры (вероятно, через python, r API)

monotone_constraints = "(0,1,1,0)"

Есть два аргумента

  • monotone_constraints - это список по длине количества функций, 1 означает монотонное увеличение, - 1 означает уменьшение, 0 означает отсутствие ограничений. Если он меньше количества функций, будет добавлен 0.

    • В настоящее время он поддерживает формат кортежей python, вы можете передавать вещи как строку при использовании r

Что нужно проверить

  • [x] Скорость исходных бустеров дерева не снижается (я немного изменил структуру кода, теоретически оптимизация шаблонов будет встраивать их, но нужно подтвердить)
  • [x] Скорость и правильность монотонной регрессии
  • [x] Производительность за счет введения этого ограничения

Известные ограничения

В настоящее время поддерживается только точный жадный алгоритм для многоядерных процессоров. Еще не доступно в распределенной версии

@tqchen Сегодня я получил запрос на работе, чтобы построить несколько GBM с монотонными ограничениями для тестирования производительности некоторых других моделей. Это было бы с потерей отклонения от твида, поэтому мне пришлось бы использовать настраиваемую функцию потерь в ее нынешнем виде.

В любом случае, похоже, это хороший шанс помочь и одновременно выполнить некоторую работу.

Основываясь на разговоре здесь , GBM (R Package) обеспечивает монотонность только локально.
Не могли бы вы прояснить, как XGBoost применяет монотонные ограничения?
Было бы здорово, если бы XGBoost мог применять глобальные ограничения.

Я не понимаю, что вы имеете в виду под локальным или глобальным ограничением, не могли бы вы уточнить?

Извините, я вставил неправильную ссылку, вот правильная (ссылка)
Каждое дерево может следовать монотонному ограничению только в определенном подмножестве интересующего объекта, так что множество деревьев в ансамбле вместе могут создать нарушение общей монотонности во всем диапазоне этого объекта.

Хорошо, в моем понимании это применяется глобально. Приглашаем вас попробовать.

Просто сделал несколько простых тестов ограничения монотонности в контексте одномерной регрессии. Вы можете найти код и очень краткую документацию здесь:

https://github.com/XiaoxiaoWang87/xgboost_mono_test/blob/master/xgb_monotonicity_constraint_testing1-univariate.ipynb

Некоторые первоначальные наблюдения:

  • Для задачи регрессии с одной переменной монотонное ограничение = +1, кажется, работает хорошо.
  • Для проблемы регрессии с одной переменной в моем наборе данных монотонное ограничение = -1, похоже, не приводит к монотонно убывающей функции. Скорее дает константу. Но это также может быть связано с отсутствием улучшений при наложении ограничения. Для подтверждения (по предложению Тианки попробуйте перевернуть набор данных и установить ограничение как +1).
  • Добавление ограничения (правильное) потенциально может предотвратить переоснащение и дать некоторое преимущество в производительности / интерпретации.

Оказывается, я ввел ошибку в случае constraint = -1. Я внес исправление, посмотрите, работает ли последняя версия. Также проверьте, работает ли он при наличии нескольких ограничений.

@tqchen Я протестировал ваше исправление для устранения ошибки, похоже, теперь оно работает.

xgboost-no-constraint
xgboost-with-constraint

Давайте подтвердим, есть ли снижение скорости по сравнению с исходной версией в некоторых стандартных наборах данных, тогда мы можем объединить их в

@tqchen Я протестировал модель с двумя переменными, одну с увеличивающимся ограничением, а

params_constrained = params.copy()
params_constrained['updater'] = "grow_monotone_colmaker,prune"
params_constrained['monotone_constraints'] = "(1,-1)"

Результаты хорошие

xgboost-two-vars-increasing
xgboost-two-vars-decreasing

Я постараюсь найти немного времени, чтобы провести некоторые временные тесты сегодня днем.

Я сделал обновление до # 1516, чтобы разрешить автоматическое определение параметров монтона, теперь пользователю нужно только передать monotone_constraints = "(0,1,1,0)" , проверьте, работает ли он.

Я объединю это, если тесты скорости пройдут успешно, и давайте перейдем к следующему этапу добавления руководств.

@madrury @ XiaoxiaoWang87

Здесь добавлены тесты для многомерного случая:

https://github.com/XiaoxiaoWang87/xgboost_mono_test/blob/master/xgb_monotonicity_constraint_testing2-multivariate.ipynb

  • Я подтверждаю, что оба монотонных ограничения = 1 и = -1 работают должным образом.
  • Ограничение монотонности не приводит к очевидному снижению скорости *
    * speed = avg [время до ранней остановки / количество итераций повышения до ранней остановки]

no constraint: 964.9 microseconds per iteration
with constraint: 861.7 microseconds per iteration

(прокомментируйте, пожалуйста, если у вас есть лучший способ провести тест скорости)

  • Необходимо соблюдать осторожность при ограничении направления для немонотонной переменной. Это может привести к снижению производительности.
  • Наблюдается сбой кода из-за Check failed: (wleft) <= (wright) при игре с разными гиперпараметрами.

Я провел пару экспериментов с синхронизацией в блокноте jupyter.

Первый тест: несколько простых смоделированных данных. Есть две функции: одна возрастающая, а другая - убывающая, но с небольшой синусоидальной волной, наложенной так, что каждая функция не является по-настоящему монотонной.

X = np.random.random(size=(N, K))
y = (5*X[:, 0] + np.sin(5*2*pi*X[:, 0])
     - 5*X[:, 1] - np.cos(5*2*pi*X[:, 1])
     + np.random.normal(loc=0.0, scale=0.01, size=N))

Вот результаты синхронизации xgboosts с монотонными ограничениями и без них. Я отключил раннюю остановку и увеличил заданное количество итераций для каждой.

Сначала без монотонных ограничений:

%%timeit -n 100
model_no_constraints = xgb.train(params, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

100 loops, best of 3: 246 ms per loop

А здесь с ограничениями монотонности

%%timeit -n 100
model_with_constraints = xgb.train(params_constrained, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

100 loops, best of 3: 196 ms per loop

Второй тест: данные о жилищном строительстве в Калифорнии от sklearn. Без ограничений

%%timeit -n 10
model_no_constraints = xgb.train(params, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

10 loops, best of 3: 5.9 s per loop

Вот ограничения, которые я использовал

print(params_constrained['monotone_constraints'])

(1,1,1,0,0,1,0,0)

И время для модели с ограничениями

%%timeit -n 10
model_no_constraints = xgb.train(params, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

10 loops, best of 3: 6.08 s per loop

@ XiaoxiaoWang87 Я подтолкнул еще один пиар, чтобы потерять контроль над wleft и wright, пожалуйста, посмотрите, это работает.
@madrury Можете ли вы также сравнить с предыдущей версией XGBoost без функции ограничения?

@tqchen Конечно. Можете ли вы порекомендовать хеш фиксации для сравнения? Должен ли я просто использовать фиксацию перед добавлением вами монотонных ограничений?

Да, подойдет предыдущий

@tqchen При восстановлении обновленной версии я получаю некоторые ошибки, которых у меня не было раньше. Я надеюсь, что причина вам ясна.

Если я попытаюсь запустить тот же код, что и раньше, я получаю исключение, вот полная трассировка:

XGBoostError                              Traceback (most recent call last)
<ipython-input-14-63a9f6e16c9a> in <module>()
      8    model_with_constraints = xgb.train(params, dtrain, 
      9                                        num_boost_round = 1000, evals = evallist,
---> 10                                    early_stopping_rounds = 10)  

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/training.pyc in train(params, dtrain, num_boost_round, evals, obj, feval, maximize, early_stopping_rounds, evals_result, verbose_eval, learning_rates, xgb_model, callbacks)
    201                            evals=evals,
    202                            obj=obj, feval=feval,
--> 203                            xgb_model=xgb_model, callbacks=callbacks)
    204 
    205 

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/training.pyc in _train_internal(params, dtrain, num_boost_round, evals, obj, feval, xgb_model, callbacks)
     72         # Skip the first update if it is a recovery step.
     73         if version % 2 == 0:
---> 74             bst.update(dtrain, i, obj)
     75             bst.save_rabit_checkpoint()
     76             version += 1

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/core.pyc in update(self, dtrain, iteration, fobj)
    804 
    805         if fobj is None:
--> 806             _check_call(_LIB.XGBoosterUpdateOneIter(self.handle, iteration, dtrain.handle))
    807         else:
    808             pred = self.predict(dtrain)

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/core.pyc in _check_call(ret)
    125     """
    126     if ret != 0:
--> 127         raise XGBoostError(_LIB.XGBGetLastError())
    128 
    129 

XGBoostError: [14:08:41] src/tree/tree_updater.cc:18: Unknown tree updater grow_monotone_colmaker

Если я отключу все для реализованного вами аргумента ключевого слова, я также получу сообщение об ошибке:

TypeError                                 Traceback (most recent call last)
<ipython-input-15-ef7671f72925> in <module>()
      8                                    monotone_constraints="(1)",
      9                                    num_boost_round = 1000, evals = evallist,
---> 10                                    early_stopping_rounds = 10)  

TypeError: train() got an unexpected keyword argument 'monotone_constraints'

удалить аргумент средства обновления и сохранить аргументы монотонного ограничения в параметрах, теперь средство обновления монотонных ограничений активируется автоматически при представлении монотонных ограничений

@tqchen Мой приятель @amontz помог мне понять это сразу после того, как я опубликовал сообщение. Я истолковал ваш комментарий как передачу monotone_constraints в качестве kwarg .train .

Он работает с этими настройками. Спасибо.

@madrury вы можете подтвердить скорость?

Также @madrury и @ XiaoxiaoWang87, поскольку эта функция сейчас близка к объединению, было бы здорово, если бы вы могли скоординировать свои действия для создания учебного пособия, знакомящего пользователей с этой функцией.

Мы не можем напрямую вывести ipy notebook в основное репо. но изображения можно отправить на https://github.com/dmlc/web-data/tree/master/xgboost и уценку на основное репо.

Нам также необходимо изменить преобразование строки внешнего интерфейса, чтобы кортеж int можно было преобразовать в формат кортежа строки, который может быть принят серверной частью.

@ hetong007 за изменения в R и @slundberg для Юлии

@tqchen Julia в настоящее время прикреплена к версии XGBoost 0.4, поэтому в следующий раз, когда мне понадобится ее использовать и у меня будет время, я обновлю привязки, если к тому времени никто еще не сделал этого. На этом этапе также можно добавить это изменение.

Вот сравнение моделей без монотонного ограничения до реализации и после.

Фиксация 8cac37 : до реализации ограничения монотонности. '
Смоделированные данные : 100 loops, best of 3: 232 ms per loop
Данные по Калифорнии : 10 loops, best of 3: 5.89 s per loop

Фиксация b1c224 : после реализации ограничения монотонности.
Смоделированные данные : 100 loops, best of 3: 231 ms per loop
Данные по Калифорнии : 10 loops, best of 3: 5.61 s per loop

Ускорение для Калифорнии после внедрения кажется мне подозрительным, но я пробовал дважды в каждом случае, и это стабильно.

Я был бы счастлив попробовать написать учебник. Я посмотрю на существующую документацию и в ближайшие несколько дней что-нибудь соберу.

Это здорово, пиар теперь официально слит на мастера. С нетерпением жду учебника

Спасибо @madrury. С нетерпением жду этого. Сообщите мне, чем я могу помочь. Я бы, конечно, хотел провести больше исследований по этой теме.

Завтра буду улучшать. Мне просто интересно, зачем общаться с C ++ через строку вместо массива.

Я тестирую из R. Я произвольно сгенерировал данные с двумя переменными и пытаюсь сделать прогноз.

Однако я обнаружил, что

  1. xgboost не ограничивает предсказание.
  2. параметр monotone_constraints делает прогноз немного другим.

Пожалуйста, укажите на это, если я допустил ошибки.

Код для его воспроизведения (протестирован на последней версии github , а не на drat ):

set.seed(1024)
x1 = rnorm(1000, 10)
x2 = rnorm(1000, 10)
y = -1*x1 + rnorm(1000, 0.001) + 3*sin(x2)
train = cbind(x1, x2)

bst = xgboost(data = train, label = y, max_depth = 2,
                   eta = 0.1, nthread = 2, nrounds = 10,
                   monotone_constraints = '(1,-1)')

pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'with constraint')
pred.ord = pred[order(train[,1])]
lines(pred.ord)

wc

bst = xgboost(data = train, label = y, max_depth = 2,
                   eta = 0.1, nthread = 2, nrounds = 10)

pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'without constraint')
pred.ord = pred[order(train[,1])]
lines(pred.ord)

woc

Ограничение было выполнено в частичном порядке. Таким образом, ограничение применяется только в том случае, если мы перемещаем ось монтона, сохраняя при этом другую ось фиксированной.

@ hetong007 Чтобы построить свои сюжеты, я

  • Создал массив, содержащий сетку x-координат, в которой я хотел предсказать эту переменную, а затем присоединиться к линейному графику. Это будет использовать seq в R.
  • Установите все остальные переменные равными их среднему значению в обучающих данных. Это будет что-то вроде colmeans в R.

Вот код Python, который я использовал для графиков, которые я включил выше, он должен довольно легко преобразоваться в эквивалентный код R.

def plot_one_feature_effect(model, X, y, idx=1):

    x_scan = np.linspace(0, 1, 100)    
    X_scan = np.empty((100, X.shape[1]))
    X_scan[:, idx] = x_scan

    left_feature_means = np.tile(X[:, :idx].mean(axis=0), (100, 1))
    right_feature_means = np.tile(X[:, (idx+1):].mean(axis=0), (100, 1))
    X_scan[:, :idx] = left_feature_means
    X_scan[:, (idx+1):] = right_feature_means

    X_plot = xgb.DMatrix(X_scan)
    y_plot = model.predict(X_plot, ntree_limit=bst.best_ntree_limit)

    plt.plot(x_scan, y_plot, color = 'black')
    plt.plot(X[:, idx], y, 'o', alpha = 0.25)

Вот как я делаю графики частичной зависимости (для произвольной модели):

  • Отсканируйте сетку значений для функции X.
  • Для каждого значения сетки признака X:

    • Установите это значение для всего столбца X объекта (всех строк). Остальные особенности без изменений.

    • Сделайте прогнозы для всех строк.

    • Возьмите среднее значение прогноза.

  • Результирующие пары (значение функции X, средний прогноз) дают вам частичную зависимость функции X.

Код:

def plot_partial_dependency(bst, X, y, f_id):

    X_temp = X.copy()

    x_scan = np.linspace(np.percentile(X_temp[:, f_id], 0.1), np.percentile(X_temp[:, f_id], 99.5), 50)
    y_partial = []

    for point in x_scan:

        X_temp[:, f_id] = point

        dpartial = xgb.DMatrix(X_temp[:, feature_ids])
        y_partial.append(np.average(bst.predict(dpartial)))

    y_partial = np.array(y_partial)

    # Plot partial dependence

    fig, ax = plt.subplots()
    fig.set_size_inches(5, 5)
    plt.subplots_adjust(left = 0.17, right = 0.94, bottom = 0.15, top = 0.9)

    ax.plot(x_scan, y_partial, '-', color = 'black', linewidth = 1)
    ax.plot(X[:, f_id], y, 'o', color = 'blue', alpha = 0.02)

    ax.set_xlim(min(x_scan), max(x_scan))
    ax.set_xlabel('Feature X', fontsize = 10)    
    ax.set_ylabel('Partial Dependence', fontsize = 12)

Спасибо за руководство! Я понял, что допустил глупую ошибку в сюжете. Вот еще один тест на одномерных данных, график кажется прекрасным:

set.seed(1024)
x = rnorm(1000, 10)
y = -1*x + rnorm(1000, 0.001) + 3*sin(x)
train = matrix(x, ncol = 1)

bst = xgboost(data = train, label = y, max_depth = 2,
               eta = 0.1, nthread = 2, nrounds = 100,
               monotone_constraints = '(-1)')
pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'with constraint', pch=20)
lines(train[ind,1], pred.ord, col=2, lwd = 5)

rplot

bst = xgboost(data = train, label = y, max_depth = 2,
               eta = 0.1, nthread = 2, nrounds = 100)
pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'without constraint', pch=20)
lines(train[ind,1], pred.ord, col=2, lwd = 5)

woc

@ hetong007 Итак, цель интерфейса R - дать пользователю возможность передавать массив R помимо строк

monotone_constraints=c(1,-1)

Пожалуйста, дайте нам знать, когда будете пиарить туториал

@ hetong007 Приглашаем вас сделать версию для r-blogger

@tqchen Извините, ребята, я неделю был в командировке.

Я отправил пару запросов на вытягивание для учебника по монотонным ограничениям. Пожалуйста, дайте мне знать, что вы думаете, я рад любой критике или критике.

Надеюсь, здесь уместно спросить: будет ли это теперь работать, если мы обновим, используя обычные git clone --recursive https://github.com/dmlc/xgboost ?

Я спросил, когда увидел новый учебник, но ничего нового об изменении самого кода. Спасибо вам всем!

да, новая функция объединяется до объединения учебника

Привет,

Я не уверен, что вы успешно реализовали глобальную монотонность, из того, что я видел в вашем коде, это больше соответствует локальной монотонности.

Вот простой пример нарушения монотонности:

`
df <- data.frame (y = c (2, rep (6,100), 1, rep (11,100)),
x1 = c (повторение (1,101), повторение (2,101)), x2 = c (1, повторение (2,100), 1, повторение (2,100)))

библиотека (xgboost)
set.seed (0)
XGB <- xgboost (data = data.matrix (df [, - 1]), label = df [, 1],
objective = " reg: linear ",
bag.fraction = 1, nround = 100, monotone_constraints = c (1,0),
eta = 0,1)

sans_corr <- data.frame (x1 = c (1,2,1,2), x2 = c (1,1,2,2))

sans_corr $ prediction <- предсказать (XGB, data.matrix (sans_corr))
`

Надеюсь, что я понимаю ваш код, и мой пример не является ложным

В настоящее время этой функции нет в API Sklearn. Не могли бы вы или кто-нибудь помочь добавить его? Спасибо!

Возможно ли принудить общую монотонность к переменной, не уточняя, должна ли она увеличиваться или уменьшаться?

@davidADSP вы можете выполнить проверку корреляции копейщика для желаемого предиктора и цели, чтобы увидеть, является ли правильное увеличение или уменьшение.

Эта функция кажется недействительной, если tree_method: hist. @tqchen любая помощь? Спасибо всем.

Как работает ограничение для мультиклассовой цели, такой как mlogloss? Поддерживается ли ограничение монотонности для потери мультикласса? Если да, то как это осуществляется. (Как для каждого класса есть дерево)

Есть ли какой-либо технический документ по алгоритму монотичности, применяемому в XGBOOST? Это глобальный или локальный? Локальный означает специфический для определенных узлов, но узлы в других частях дерева могут нарушить общую монотонность. Также может кто-нибудь помочь мне в понимании строки L412-417 . Почему "w" ограничено - сверху и снизу. Как это помогает сохранить монотонность. Строка 457 - Почему используется «мид»?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги