Xgboost: [Nueva función] Restricciones monótonas en la construcción de árboles

Creado en 27 ago. 2016  ·  46Comentarios  ·  Fuente: dmlc/xgboost

Recibí algunas solicitudes sobre el soporte de restricciones monótonas en cierta función con respecto a la salida,

es decir, cuando se fijan otras características, obligar a que la predicción sea monótona aumentando con respecto a la característica especificada determinada. Estoy abriendo este número para ver el interés general sobre esta función. Puedo agregar esto si hay suficiente interés en esto,

Necesitaría la ayuda de voluntarios de la comunidad para probar la función beta y contribuir con documentos y tutoriales sobre el uso de esta función. Responde el problema si estás interesado.

Comentario más útil

Actualmente, esta función no está en la API de Sklearn. ¿Puede usted o alguien ayudar a agregarlo? ¡Gracias!

Todos 46 comentarios

Se proporciona una versión experimental en https://github.com/dmlc/xgboost/pull/1516. Para usar esto antes de que se fusione, clone el repositorio https://github.com/tqchen/xgboost ,

Active las siguientes opciones (probablemente sea posible a través de python, r API)

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

Hay dos argumentos

  • monotone_constraints es una lista en la longitud del número de características, 1 indica monótona creciente, - 1 significa decreciente, 0 significa sin restricción. Si es más corto que el número de funciones, se rellenará 0.

    • Actualmente es compatible con el formato de tupla de Python, puede pasar cosas como una cadena cuando usa r

Cosas para verificar

  • [x] La velocidad de los impulsores de árbol originales no se ralentiza (cambié un poco la estructura del código, en teoría, la optimización de las plantillas las alineará, pero necesito confirmar)
  • [x] La velocidad y la exactitud de la regresión monótona.
  • [x] El rendimiento al introducir esta restricción

Limitaciones conocidas

Actualmente solo se admite el algoritmo codicioso exacto en varios núcleos. Aún no disponible en versión distribuida

@tqchen Recibí una solicitud en el trabajo hoy para construir algunos GBM con restricciones monótonas para probar el rendimiento de algunos otros modelos. Esto sería con una pérdida por desviación de tweedie, por lo que tendría que optar por una función de pérdida personalizada tal como está hoy.

En cualquier caso, parece una buena oportunidad para ayudar y trabajar al mismo tiempo.

Basado en la charla aquí , GBM (R Package) solo refuerza la monotonicidad localmente.
¿Podría aclarar cómo XGBoost aplica restricciones monótonas?
Sería genial si XGBoost pudiera hacer cumplir las restricciones globales.

No entiendo lo que quiere decir con restricción local o global, ¿puede darnos más detalles?

Lo siento, pego el enlace incorrecto, aquí está el correcto (Enlace)
Cada árbol solo puede seguir una restricción monótona en cierto subconjunto de la característica interesada, por lo que muchos árboles en conjunto pueden crear una violación de la monotonicidad general en todo el rango de esa característica.

De acuerdo, a mi entender, se aplica a nivel mundial. Le invitamos a probarlo.

Acabo de hacer algunas pruebas simples de restricción de monotonicidad en el contexto de una regresión univariante. Puede encontrar el código y una documentación muy breve aquí:

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

Algunas observaciones iniciales:

  • Para un problema de regresión de una sola variable, la restricción monótona = +1 parece funcionar bien
  • Para un problema de regresión de una sola variable, en mi conjunto de datos, la restricción monótona = -1 no parece producir una función decreciente monótona. Más bien, da una constante. Pero esto también puede deberse a la falta de mejora al forzar la restricción. Por confirmar (según la sugerencia de Tianqi, intente voltear el conjunto de datos y establezca la restricción como +1).
  • Agregar la restricción (correctamente) puede potencialmente prevenir el sobreajuste y traer algún beneficio de desempeño / interpretación.

Resulta que introduzco un error en el caso de la restricción = -1. Presioné una solución, compruebe si la versión más reciente funciona bien. Compruebe también si funciona cuando hay varias restricciones

@tqchen Probé tu solución para el error de decresing, parece que está funcionando ahora.

xgboost-no-constraint
xgboost-with-constraint

Confirmemos si hay una disminución de velocidad en comparación con la versión original en algunos de los conjuntos de datos estándar, luego podemos fusionarlos en

@tqchen Probé un modelo de dos variables, una con una restricción creciente y otra con una disminución:

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

Los resultados son buenos

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

Intentaré encontrar un poco de tiempo para hacer algunas pruebas de cronometraje esta tarde.

Hice una actualización a # 1516 para permitir la detección automática de opciones montone, ahora el usuario solo necesita pasar monotone_constraints = "(0,1,1,0)" , verifique si funciona.

Combinaré esto si las pruebas de velocidad van bien, y pasemos a la siguiente etapa para agregar tutoriales

@madrury @ XiaoxiaoWang87

Se agregaron pruebas para el caso multivariado aquí:

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

  • Confirmo ahora que tanto la restricción monótona = 1 como = -1 funcionan como se esperaba.
  • Limitar la monotonicidad no conduce a una degradación evidente de la velocidad *
    * speed = avg [tiempo hasta la parada anticipada / número de iteraciones de impulso hasta la parada anticipada]

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

(comente si tiene una mejor manera de hacer la prueba de velocidad)

  • Debe tener cuidado al restringir la dirección de una variable no monótona. Esto puede provocar una degradación del rendimiento.
  • Ver que el código falla debido a Check failed: (wleft) <= (wright) al jugar con diferentes hiperparámetros.

Ejecuté un par de experimentos de cronometraje en un cuaderno jupyter.

Primera prueba: algunos datos simulados simples. Hay dos características, una creciente y otra decreciente, pero con una pequeña onda sinusoidal superpuesta para que cada característica no sea realmente monótona.

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))

A continuación, se muestran los resultados de sincronización de xgboosts con y sin restricciones monótonas. Apagué la parada anticipada y aumenté un número determinado de iteraciones para cada una.

Primero sin restricciones monótonas:

%%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

Y aquí con limitaciones de monotonicidad

%%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

Segunda prueba: datos de California hHousing de sklearn. Sin restricciones

%%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

Aquí están las restricciones que utilicé

print(params_constrained['monotone_constraints'])

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

Y el momento para el modelo restringido

%%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 He presionado a otro RP para que pierda el control de wleft y wright, por favor vea que funciona.
@madrury ¿También se puede comparar con la versión anterior de XGBoost sin la función de restricción?

@tqchen Seguro. ¿Puedes recomendar un hash de confirmación para compararlo? ¿Debería usar el compromiso antes de agregar las restricciones monótonas?

Sí, el anterior servirá

@tqchen Al reconstruir la versión actualizada, recibo algunos errores que no tenía antes. Espero que la razón te salte claramente.

Si trato de ejecutar el mismo código que antes, obtengo una excepción, aquí está el rastreo completo:

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

Si cambio todo por el argumento de palabra clave que implementó, también obtengo un error:

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'

elimine el argumento del actualizador y mantenga los argumentos de restricción monótonos en los parámetros, ahora que el actualizador de restricciones monótonas se activa automáticamente cuando se presentan restricciones monótonas

@tqchen Mi amigo @amontz me ayudó a entender eso inmediatamente después de publicar el mensaje. Había interpretado su comentario como pasar monotone_constraints como un kwarg a .train .

Funciona con esos ajustes. Gracias.

@madrury ¿puedes confirmar la velocidad?

También @madrury y @ XiaoxiaoWang87, dado que esta función está a punto de fusionarse, sería genial si pudiera coordinar la creación de un tutorial que presente esta función a los usuarios.

No podemos llevar directamente ipy notebook al repositorio principal. pero las imágenes se pueden enviar a https://github.com/dmlc/web-data/tree/master/xgboost y se pueden reducir al repositorio principal.

También necesitamos cambiar la conversión de cadenas de la interfaz de front-end, de modo que int tuple se pueda convertir al formato de tuplas de cadenas que pueda ser aceptado por el backend.

@ hetong007 para cambios en R y @slundberg para Julia

@tqchen Julia está actualmente adjunta a la versión 0.4 de XGBoost, así que la próxima vez que necesite usarlo y tener tiempo reservado, actualizaré los enlaces si nadie más lo ha hecho para entonces. En ese momento, este cambio también se puede agregar.

Aquí está la comparación entre modelos _sin_ una restricción monótona desde antes de la implementación hasta después.

Confirmar 8cac37 : antes de la implementación de la restricción monótona. '
Datos simulados : 100 loops, best of 3: 232 ms per loop
Datos de California : 10 loops, best of 3: 5.89 s per loop

Confirmar b1c224 : después de la implementación de la restricción monótona.
Datos simulados : 100 loops, best of 3: 231 ms per loop
Datos de California : 10 loops, best of 3: 5.61 s per loop

La aceleración para California después de la implementación me parece sospechosa, pero la intenté dos veces en cada sentido y es consistente.

Me encantaría intentar escribir un tutorial. Revisaré la documentación existente y armaré algo en los próximos días.

Esto es genial, el RP ahora está oficialmente fusionado con el maestro. Esperando ver el tutorial

Gracias @madrury. Espero que llegue. Hágame saber en qué puedo ayudar. Ciertamente estaría dispuesto a tener más estudios sobre este tema.

Lo mejoraré mañana. Solo tengo curiosidad sobre la razón de comunicarme con C ++ a través de una cadena en lugar de una matriz.

Estoy probando desde R. Genere aleatoriamente datos de dos variables e intento hacer una predicción.

Sin embargo, encontré que

  1. xgboost no limita la predicción.
  2. el parámetro monotone_constraints hace que la predicción sea ligeramente diferente.

Por favor, indíquelo si cometí algún error.

El código para reproducirlo (probado en la última versión de github , no desde 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

La restricción se realizó en el pedido parcial. Por lo tanto, la restricción solo se aplica si estamos moviendo el eje montone, manteniendo el otro eje fijo

@ hetong007 Para hacer mis parcelas yo

  • Creé una matriz que contiene la cuadrícula de coordenadas x en la que quería predecir esa variable y luego unirme al gráfico de líneas. Esto usaría seq en R.
  • Establezca todas las demás variables iguales a su valor promedio en los datos de entrenamiento. Esto sería algo así como colmeans en R.

Aquí está el código de Python que usé para los gráficos que incluí arriba, debería convertirse fácilmente a un código R equivalente.

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)

Así es como hago las gráficas de dependencia parcial (para un modelo arbitrario):

  • Escanee una cuadrícula de valores para la característica X.
  • Para cada valor de cuadrícula de la característica X:

    • Establezca toda la columna X de la entidad (todas las filas) en este valor. Otras características sin cambios.

    • Haz predicciones para todas las filas.

    • Toma el promedio de predicción.

  • Los pares resultantes (valor de la característica X, predicción promedio) le dan la dependencia parcial de la característica X.

Código:

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)

¡Gracias por la orientación! Me di cuenta de que cometí un error tonto en la trama. Aquí hay otra prueba con datos univariados, el gráfico parece estar bien:

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 Entonces, el objetivo en la interfaz R es permitir al usuario pasar la matriz R además de las cadenas

monotone_constraints=c(1,-1)

Háganos saber cuando sea RP el tutorial

@ hetong007 También eres más que bienvenido para hacer una versión de r-blogger

@tqchen Lo siento chicos, he estado en un viaje de trabajo durante la semana.

Envié un par de solicitudes de extracción con un tutorial de restricción monótona. Por favor déjeme saber lo que piensa, estoy contento con cualquier crítica o crítica.

Con suerte, es apropiado preguntar esto aquí: ¿funcionará ahora si actualizamos usando el git clone --recursive https://github.com/dmlc/xgboost habitual?

Pregunto cuando vi el nuevo tutorial, pero nada nuevo sobre un cambio en el código en sí. ¡Gracias a todos!

sí, la nueva función se fusiona antes de fusionar el tutorial

Hola,

No estoy seguro de que hayas implementado con éxito la montonicidad global, por lo que he visto en tu código, corresponde más a una monotonicidad local.

Aquí hay un ejemplo simple que rompe la monotonicidad:

'
gl <- data.frame (y = c (2, rep (6,100), 1, rep (11,100)),
x1 = c (rep (1,101), rep (2,101)), x2 = c (1, rep (2,100), 1, rep (2,100)))

biblioteca (xgboost)
set.seed (0)
XGB <- xgboost (data = data.matrix (df [, - 1]), label = df [, 1],
objetivo = " reg: lineal ",
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 <- predecir (XGB, data.matrix (sans_corr))
'

Espero que mi comprensión de su código y mi ejemplo no sea falso

Actualmente, esta función no está en la API de Sklearn. ¿Puede usted o alguien ayudar a agregarlo? ¡Gracias!

¿Es posible imponer una monotonicidad general a una variable, sin especificar si debería ser creciente o decreciente?

@davidADSP puede hacer una verificación de correlación de

Esta característica parece no ser válida cuando 'tree_method': 'hist'. @tqchen ¿ alguna ayuda? Gracias a todos.

¿Cómo funciona la restricción para objetivos multiclase como mlogloss? ¿Se admite la restricción de monotonicidad para la pérdida multiclase? Si es así, ¿cómo se hace cumplir? (En cuanto a cada clase hay un árbol)

¿Existe algún documento técnico sobre el algoritmo de monoticidad aplicado en XGBOOST? ¿Es global o local? Los medios locales específicos para ciertos nodos, pero los nodos en otras partes del árbol pueden crear una violación de la monotonicidad general. También puede alguien ayudarme a comprender la línea L412-417 . Por qué "w" está acotado: superior e inferior. Cómo esto ayuda a mantener la monotonicidad. Línea 457 - ¿Por qué se usa "mid"?

¿Fue útil esta página
0 / 5 - 0 calificaciones