Xgboost: ¿Cómo genera XGBoost probabilidades de clase en un problema de clasificación multiclase?

Creado en 8 nov. 2016  ·  15Comentarios  ·  Fuente: dmlc/xgboost

Cuando intento volcar un árbol XGBoost entrenado, lo obtengo en el siguiente formato:

0:[f37<39811] 
    1:[f52<199.5] 
        3:leaf=-0.021461
        4:[f2<0.00617284] 
            9:leaf=-0.0118755
            10:[f35<-83.548] 
                19:[f13<0.693844] 
                    23:[f37<1831]
                        29:leaf=-0
                        30:leaf=0.123949
                    24:leaf=0.0108628
                20:[f35<-74.6198] 
                    25:leaf=-0.0175897
                    26:leaf=0.051898
    2:leaf=-0.0239901

Si bien está claro cuál es la estructura del árbol, no está claro cómo interpretar los valores de las hojas. Para un problema de clasificación _ binario _ y una función de costo de pérdida logarítmica, los valores hoja se pueden convertir a probabilidades de clase usando una función logística: 1 / (1 + exp (valor)). Sin embargo, para un problema de clasificación _multiclase_ , no hay información sobre a qué clase pertenecen esos valores y, sin esa información, no está claro cómo calcular las probabilidades de clase.

¿Algunas ideas? ¿O hay alguna otra función para sacar esa información de los árboles entrenados?

Comentario más útil

Esa es una muy buena observación, que no había notado antes. Es cierto, la cantidad de árboles en el modelo es n_estimator x n_class . Sin embargo, para averiguar el orden de los árboles, utilicé el siguiente ejemplo de juguete:

x = np.concatenate([np.ones([10,1])*np.array([1,0,0]),np.ones([10,1])*np.array([0,1,0]),np.ones([10,1])*np.array([0,0,1])], axis=0)
y = np.array([['a']*10+['c']*10+['b']*10]).reshape([30,1])
model = xgb.XGBClassifier(n_estimators=2, objective='mlogloss').fit(x, y)
model.booster().dump_model('trees.txt')

que básicamente crea un conjunto de datos de entrenamiento donde [1,0,0] se asigna a 'a', [0,1,0] se asigna a 'c' y [0,0,1] se asigna a 'b'. Cambié intencionalmente el orden de 'b' y 'c' para ver si xgboost clasifica las clases en función de sus etiquetas antes de tirar los árboles.

Aquí está el modelo volcado resultante:

booster[0]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[1]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[2]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[3]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[4]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[5]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941

y aquí están las conclusiones:

  1. Como mencionaste, aunque n_estimators = 2, el número de árboles es 2x3 = 6.
  2. Al observar el orden de las características que usan los árboles, parece que el primer árbol pertenece a la primera clase, el segundo árbol a la segunda clase, y así sucesivamente, hasta la última clase. Luego, se repite el mismo patrón hasta cubrir todos los estimadores.
  3. El orden de las clases parece ser coherente con lo que devuelve numpy.unique() ; aquí es alfabético, y es por eso que el primer árbol usa f0 mientras que el segundo usa f2 .

Sería bueno si esta información se agregara a la documentación de xgboost.

Todos 15 comentarios

También tengo la misma pregunta. Lo único que noté es que si tiene 20 clases y establece el número de estimadores en 100, entonces tendrá 20 * 100 = 2000 árboles impresos en su modelo. Ahora supongo que los primeros 100 estimadores clasifican la primera clase frente a otros. Los siguientes 100 estimadores clasifican la segunda clase frente a otros, etc.

No se puede confirmar, pero tal vez podría ser algo como esto.

Esa es una muy buena observación, que no había notado antes. Es cierto, la cantidad de árboles en el modelo es n_estimator x n_class . Sin embargo, para averiguar el orden de los árboles, utilicé el siguiente ejemplo de juguete:

x = np.concatenate([np.ones([10,1])*np.array([1,0,0]),np.ones([10,1])*np.array([0,1,0]),np.ones([10,1])*np.array([0,0,1])], axis=0)
y = np.array([['a']*10+['c']*10+['b']*10]).reshape([30,1])
model = xgb.XGBClassifier(n_estimators=2, objective='mlogloss').fit(x, y)
model.booster().dump_model('trees.txt')

que básicamente crea un conjunto de datos de entrenamiento donde [1,0,0] se asigna a 'a', [0,1,0] se asigna a 'c' y [0,0,1] se asigna a 'b'. Cambié intencionalmente el orden de 'b' y 'c' para ver si xgboost clasifica las clases en función de sus etiquetas antes de tirar los árboles.

Aquí está el modelo volcado resultante:

booster[0]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[1]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[2]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0674157
    2:leaf=0.122449
booster[3]:
0:[f0<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[4]:
0:[f2<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941
booster[5]:
0:[f1<0.5] yes=1,no=2,missing=1
    1:leaf=-0.0650523
    2:leaf=0.10941

y aquí están las conclusiones:

  1. Como mencionaste, aunque n_estimators = 2, el número de árboles es 2x3 = 6.
  2. Al observar el orden de las características que usan los árboles, parece que el primer árbol pertenece a la primera clase, el segundo árbol a la segunda clase, y así sucesivamente, hasta la última clase. Luego, se repite el mismo patrón hasta cubrir todos los estimadores.
  3. El orden de las clases parece ser coherente con lo que devuelve numpy.unique() ; aquí es alfabético, y es por eso que el primer árbol usa f0 mientras que el segundo usa f2 .

Sería bueno si esta información se agregara a la documentación de xgboost.

Estoy cerrando este problema ahora.

Todavía tengo un comentario aquí. Estoy de acuerdo con cómo se estructuran los árboles en el caso de clases múltiples. Pero, ¿cómo se convierten los valores de las hojas en probabilidades?

Parece que para el caso binario debe aplicar 1 / (1 + exp (valor)), mientras que para el caso de clases múltiples debe aplicar 1 / (1 + exp (-valor)).

Eso parece ser cierto, mirando los ejemplos. ¡Gracias por el consejo!

Pero no entiendo por qué se definen de manera diferente para casos binarios y multiclase.

Sí, de hecho, es muy extraño y confuso por decir lo menos.

Llegué a esa conclusión probándolo en el conjunto de datos de automóviles de la UCI (donde clasifica los automóviles en inaceptables, aceptables, buenos o muy buenos).

Sería bueno tener una característica adicional en la que pueda convertir los árboles en árboles sklearn o algo así (actualmente tengo una base de código donde puedo convertir estos árboles xgb y árboles sklearn a mi propio árbol de decisión definido). De estos últimos, los árboles sklearn, estoy al menos 100% seguro de que se han convertido correctamente. Para los árboles xgb, la duda permanece.

Estoy de acuerdo en que sería una gran característica.

@GillesVandewiele @sosata : entonces en esa logística (digamos para la clase i ) [ 1/(1+exp(-value)) ], el value es la suma de todas las puntuaciones de las hojas correspondientes a los árboles asociados de esa clase en particular con la muestra?

Para el ejemplo de mi comentario anterior, si trato de predecir las probabilidades de clase para [1 0 0]:

print(model.predict_proba(np.array([[1,0,0]])))

generará estos resultados:

[[ 0.41852772  0.29073614  0.29073614]]

No hay forma de que 1/(1+exp(-value)) genere esto. La única forma es que estas probabilidades se generen mediante una función _softmax_ de los valores sumados en las clases:

p[i] = exp(val[i])/(exp(val[1])+exp(val[2])+...+exp(val[N]))

donde i es la clase objetivo (de N clases), y val [i] es la suma de todos los valores generados a partir de los árboles que pertenecen a esa clase.
En nuestro ejemplo:

print(np.exp(+0.122449+0.10941)/(np.exp(+0.122449+0.10941)+np.exp(-0.0674157-0.0650523)+np.exp(-0.0674157-0.0650523)))
print(np.exp(-0.0674157-0.0650523)/(np.exp(+0.122449+0.10941)+np.exp(-0.0674157-0.0650523)+np.exp(-0.0674157-0.0650523)))
print(np.exp(-0.0674157-0.0650523)/(np.exp(+0.122449+0.10941)+np.exp(-0.0674157-0.0650523)+np.exp(-0.0674157-0.0650523)))

Generará:

0.418527719063
0.290736140469
0.290736140469

que es exactamente lo que nos dio la función predict_proba ().

Eso parece correcto @sosata

En mi caso, quería convertir cada uno de los árboles individualmente (por lo tanto, no usar valores de hojas de otros árboles). Allí, una función sigmoidea pareció hacer el trabajo.

@sosata ¿Hay

@mlxai También solo probé la API de Python y no recuerdo haber podido obtenerlos por clase.

Sin embargo, en mi opinión, la definición de importancia de la función en XGBoost como el número total de divisiones en esa función es un poco simplista. Personalmente, calculo la importancia de la característica quitando esa característica del conjunto de características y calculando la caída (o aumento) resultante en la precisión, cuando el modelo se entrena y prueba sin esa característica. Hago esto para todas las funciones y defino la "importancia de la función" como la cantidad de caída (o menos aumento) en la precisión. Dado que el entrenamiento y las pruebas se pueden realizar varias veces con diferentes subconjuntos de entrenamiento / prueba para cada eliminación de característica, también se pueden estimar los intervalos de confianza de importancia de característica. Además, al calcular la importancia por separado para cada clase, obtienes importancias de características por clase. Esto ha producido resultados más significativos en mis proyectos que contar el número de divisiones.

@sosata Tu ejemplo es muy intuitivo, pero todavía no sé cómo se te

@chrisplyn Evalúa todos los árboles de decisión de su conjunto que pertenecen a una clase específica y suma las puntuaciones resultantes.

Como @sosata dijo correctamente anteriormente: la cantidad de árboles en su conjunto será igual a n_esimators * n_classes

@sosata tu ejemplo es muy claro. ¡Muchos gracias!

@chrisplyn La instancia de predicción [1,0,0] se clasificará mediante el refuerzo [0 ~ 5] y obtendrá los valores de hoja [0.122449, -0.0674157, -0.0674157, 0.10941, -0.0650523, -0.0650523] respectivamente.
Para el refuerzo [0, 3] pertenece a la clase 0, el refuerzo [1, 4] pertenece a la clase 1 y el refuerzo [2, 5] pertenece a la clase 2, por lo que val [0] = (0.122449 + 0.10941), val [1 ] = (-0.0674157 + -0.0650523), val [2] = (-0.0674157 + -0.0650523).
¿Está más claro?

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