Cómo realizar una clasificación explicable de aprendizaje automático - Sin árboles

Crédito: Pixabay

Reglas estrictas y claras … nos parecen algo en el fondo, ocultas en el medio del entendimiento.
- Ludwig Wittgenstein

Los árboles de decisión son una técnica popular para la clasificación. Son intuitivos, fáciles de interpretar y, a menudo, se desempeñan bien fuera de la caja.

Los modelos de árbol son caminos de reglas que los humanos pueden entender. En ciertos contextos, ser capaz de proporcionar una explicación para sus predicciones: la solicitud de préstamo del demandante fue rechazada porque estaban en proceso de quiebra, en lugar de, algo-algo-algo punto-producto - puede ser una ventaja.

Pero existe otra clase de algoritmos generadores de reglas con los que quizás no esté familiarizado y que valga la pena agregar a su conjunto de herramientas, conocido como el conjunto de reglas.

En un proyecto reciente, se clasificó el audio por el compositor de música clásica, la implementación de mi conjunto de reglas venció a DecisionTreeClassifier de sklearn y al SVD optimizado para la búsqueda en cuadrícula y al bosque aleatorio.

Veamos cuándo es posible que desee considerar los modelos de conjunto de reglas, cómo funcionan y cómo se pueden usar. en su propio código, incluso con un nuevo paquete Python, desarrollé recientemente que puede usar para la clasificación.

Motivación

A pesar de sus muchas ventajas, el árbol de decisiones

Los árboles aprenden al comenzar con el conjunto de entrenamiento completo y al agregar con avidez cualquier condición que maximice la pureza de clase de cada nodo infantil. A medida que descendemos por el árbol, cada nodo agrega una condición, subdividiendo nuestros datos de entrenamiento en subgrupos cada vez más pequeños:

Modelo de árbol de decisión generado por sklearn en el conjunto de datos de setas .

En solo unos pocos pasos de decisión, hemos pasado de una raíz con miles de ejemplos a hojas con tamaños de muestra tan bajos como 29, 11, 2 - incluso 1.

¿Cómo? Piense en cómo la búsqueda de árbol binario se adapta bien porque la profundidad del árbol solo crece logarítmicamente con el número de nodos. La otra cara de esto para los árboles de decisión de entrenamiento es que el número de nodos crece a una velocidad exponencial con la profundidad. Por lo tanto, nuestros subconjuntos de capacitación se reducen rápidamente y, a la larga, carecen de tamaños de muestra estadísticamente válidos.

Por razones similares, los árboles son “frágiles” en el sentido de que pequeños cambios en el conjunto de capacitación alteran las reglas de nivel superior, produciendo efectos dominicales en todo el conjunto

Existen varias técnicas populares para tratar la fragilidad y el sobreajuste de árboles. Podemos podar el árbol, ya sea deteniendo el crecimiento temprano de acuerdo con algún umbral, o reduciendo su tamaño una vez completado. O podríamos construir un conjunto de bosques aleatorios de miles de árboles, con un poco de gasto para la velocidad de entrenamiento y la capacidad de explicación.

Los conjuntos de reglas son similares a los árboles de decisión, pero debido a que no son jerárquicos, con decisiones ordenadas y sub-ramificadas. tienen el potencial de eludir algunas de estas desventajas.

Los aprendices del conjunto de reglas también tienden a producir modelos más compactos.

Algunas diferencias importantes entre los árboles y los conjuntos de reglas

Entonces, ¿qué es un conjunto de reglas?

Un conjunto de reglas es simplemente disyuntivas (o) de conjunciones (y).

Por ejemplo, un modelo que describe si su hijo puede persuadirlo para que salga a comer pastel de helado podría verse así:

Si:
Estoy bien y tenemos tiempo suficiente para comprar un helado; o
tu capacidad de recuperación está agotada y no tienes ganas de tratar conmigo; o
te sientes magnánimo,
Luego:
Torta de helado.

Podemos expresar este conjunto de reglas simbólicamente:

(Estoy bien ^ tenemos suficiente tiempo) V
(resiliencia desgastada ^ simplemente no puedes ni siquiera) V
(magnánimo)

Y Pythonically:

[[(behavior=good),(time=True)]
[(resilience=thin),(you=can’t even)]
[(feeling=magnanimous)]]

Cómo entrenar un modelo de conjunto de reglas

En lugar de dividir los datos de entrenamiento en subconjuntos de subconjuntos con recursión, los conjuntos de reglas crecen iterativamente al entrenar en todos los datos de entrenamiento aún por examinar.

Para obtener una idea básica de cómo funciona el entrenamiento, primero echemos un vistazo a un sencillo algoritmo de aprendizaje de conjunto de reglas conocido como IREP.

Después de dividir nuestros datos en un conjunto de entrenamiento y conjunto de prueba, entrenamos un modelo en nuestro conjunto de entrenamiento con los siguientes pasos:

1. Divida su conjunto de entrenamiento en un “grow-tal” y un “pruneset”

2. Crecer una regla (ganancia de información)

3. Pode la regla (métrica de error reducida)

4. Elimine los ejemplos que cubre la nueva regla de su conjunto de entrenamiento y repita los pasos 1, 2 y 3 hasta que empiece a empeorar las cosas (métrica de precisión).

En el paso 1, tomamos nuestros datos de entrenamiento y los dividimos aleatoriamente 2 / 3–1 / 3. Usaremos la primera parte para hacer crecer una regla, y la segunda parte para recortarla. ¡Algo así como la validación cruzada nos obliga a dejar de lado los pliegues de evaluación, la división garantiza que no podamos realizar recortes con los mismos datos que utilizamos para crecer!

A continuación, desarrollamos una regla agregando codiciosamente condicionales que maximizan FOIL ganancia de información (fórmula siguiente). A medida que nuestra regla adquiere más y más condicionales, se vuelve cada vez más estricta y descarta más y más ejemplos de clase negativa. (Recuerde que una regla es un conjunto de “y”, más “y” significa más rigurosidad). Nos detenemos cuando la regla no cubre más ejemplos negativos.

Ganancia de información de FOIL. p0 (n0) es el número de ejemplos positivos (negativos) cubiertos por una regla existente, p1 (n1) el número cubierto por la nueva regla propuesta.

Ahora es el momento de eliminar la regla que acabamos de desarrollar. Tratamos de podar cada uno de sus condicionales con codicia en orden inverso, eligiendo la regla que maximiza algunas métricas de poda, como esta:

Criterio de poda. p (n) es el número de ejemplos positivos (negativos) cubiertos por la regla, P (N) es el número total. Las diferentes versiones usan diferentes métricas de poda.

¡Acabamos de crecer y podar nuestra primera regla! Ahora iteramos. De nuestro conjunto de entrenamiento, elimine los ejemplos cubiertos por la nueva regla. Continúe desarrollando nuevas reglas, haciendo que nuestro conjunto de reglas sea cada vez más indulgente, hasta que crezcamos una cuya precisión sea inferior al 50%.

Aquí hay una visualización de todo el proceso:

Cultivo-poda de un conjunto de reglas con cobertura iterativa.

Mi nuevo paquete de reglas, wittgenstein, implementa IREP, así como otro algoritmo de reglas llamado RIPPER. Además de sonar como una banda de heavy metal, RIPPER sigue siendo el estado de la técnica para esta técnica.

El algoritmo es un poco más complicado que el IREP, pero aquí están las principales diferencias:

  • Una detención más teóricamente rigurosa condición: En lugar de usar la métrica de poda para decirnos cuándo dejar de desarrollar nuevas reglas, RIPPER toma prestada una heurística de la teoría de la información utilizada en el algoritmo del árbol de decisión C4.5 de Quinlan conocida como longitud de descripción. La idea es que podemos medir la complejidad total (en bits) en cualquier etapa de nuestro proceso de modelado como la complejidad de nuestro modelo tentativo más la de todos los ejemplos que no logra capturar. A medida que nuestro conjunto de reglas crece en longitud y precisión, la complejidad del modelo aumenta y la complejidad de la cantidad de ejemplos que no logra capturar disminuye. Para evitar el ajuste excesivo, dejamos de aumentar las reglas una vez que la complejidad total ha crecido más allá de algún umbral. La longitud de la descripción guía el acto de equilibrio entre minimizar el error de entrenamiento y minimizar la complejidad del modelo.
  • Calcular la longitud de la descripción de un modelo es complicado y costoso. Pero lo esencial es que la complejidad del modelo se basa en la discriminación . Una regla con más condicionales es más compleja que una con menos, ya que es una regla que selecciona sus condicionales de un conjunto mayor de posibilidades.
k es el número de condiciones en la regla, n el número de condiciones posibles . r es k / n. || k || es el número de bits necesarios para enviar k (es decir, log2 (k)). El factor 0.5 es tener en cuenta las posibles redundancias.
  • La fórmula de longitud de la descripción de las excepciones es más sencilla. Elegimos las combinaciones de falsos positivos y de falsos negativos de nuestras predicciones positivas y negativas. (La fórmula usa combinaciones en lugar de permutaciones, ya que el orden no importa). Log₂ convierte de valores decimales en bits:
p es el número de ejemplos clasificados como positivos, n el número clasificado como negativo. fp es el número de falsos positivos, fn falsos negativos.
  • Optimización del modelo: Una vez que hemos crecido un conjunto de reglas inicial, podemos usar nuestro modelo para reevaluar la contribución de cada regla de una manera más holística. Consideramos reemplazar cada regla con un par de alternativas: un reemplazo completamente nuevo para adultos así como una revisión para adultos mayores y podados del original. Nuestro modelo optimizado utiliza cualquiera de los tres: original, reemplazo o revisión, es el mejor. (“Mejor” aquí es un poco complicado y algo espantoso de implementar. Significa que cualquier regla resultaría en la descripción más pequeña para el conjunto de reglas, basada en la longitud de descripción más pequeña posible que podríamos obtener si eliminamos todas las demás reglas que aumentan Descripción de la duración.) Podemos repetir la fase de optimización tantas veces como queramos, pero el artículo original sugiere dos iteraciones.

¿Y no es también el caso en el que jugamos y - inventamos las reglas a medida que avanzamos?
- Ludwig Wittgenstein

  • Envuelva las cosas: Si es necesario, crezca algunas reglas más para cubrir cualquier ejemplo de entrenamiento positivo que nuestro modelo optimizado ya no cubra. Finalmente, elimine cualquier regla que no mejore la longitud de la descripción.

Usando los conjuntos de reglas en su código

Los usuarios de Java que quieran usar un conjunto de reglas pueden usar la implementación RIPPER de Weka JRip . También hay envoltorios Weka para Python y R.

Los usuarios de Python también pueden probar wittgenstein. (Podría haber otros paquetes de Python para estos algoritmos particulares, pero no pude encontrar ninguno). El repositorio de github está aquí .

Para instalar desde la línea de comandos:

 pip instale wittgenstein

Este es un ejemplo de uso rápido, utilizando el delicioso conjunto de datos de setas envenenadas . Nuestro objetivo es producir un conjunto de reglas que puedan discernir qué hongos son venenosos.

Comencemos cargando nuestro marco de datos en pandas:

 >>> importamos pandas como pd 
 >>> df = pd.read_csv (mushroom.csv)

Y train-test-splitting nuestros datos:

 >>> from sklearn.model_selection import train_test_split 
 >>> train, test = train_test_split (df, test_size = .33, .. . random_state = 42)

Wittgenstein usa una sintaxis de puntuación de ajuste que es similar a la de scikit-learn. Entrenaremos un clasificador RIPPER, con la clase positiva definida como venenosa.

 >>> importamos wittgenstein como lw 
 >>> clf = lw.RIPPER () 
 >>> clf.fit (tren , class_feat = & # 039; Poisonous / Edible & # 039 ;, pos_class = & # 039; p & # 039 ;, 
 ... random_state = 42)

Durante la inicialización / ajuste, podemos pasar algunos parámetros opcionales :

  • prune_size : cambia la proporción de crecimiento / poda. Si desea omitir la etapa de poda (¡emocionante, pero no recomendado!), Puede configurar esto en Ninguno cuando use IREP.
  • k : el número de ejecuciones de optimización
  • dl_allowance : el umbral de detención de la complejidad
  • verbosidad (1–5) : use esto si tiene curiosidad por ver cómo se generan las reglas. (Cada nivel de verbosidad se explica en las cadenas de documentación).
  • n_discretize_bins : wittgenstein detectará automáticamente y discretizará las funciones continuas para usted; use esto si desea controlar el número de ubicaciones.

Usted puede probar un modelo utilizando la métrica predeterminada (precisión), o pasando su propia métrica de puntuación. Importemos precisión y recordemos de scikit-learn. También examinaremos la complejidad del modelo contando el número de condiciones.

 >>> # Característica de clase de destino dividida 
 >>> X_test = test.drop (& # 039; Venenoso / comestible & # 039 ;, axis = 1) 
 >>> y_test = prueba ['Poisonous/edible']
 >>> # Recopile métricas de rendimiento 
 >>> de sklearn.metrics import precision_score, recall_score 
 >>> precision = clf.score (X_test, y_test , precision_score) 
 >>> recall = clf.score (X_test, y_test, recall_score) 
 >>> cond_count = clf.ruleset_.count_conds ()
 >>> print (f & # 039; precisión: {precision} memoria: {memoria} 
 ... conds: {cond_count} & # 039;)
 precisión: 0.9938 ..., memoria: 0.9977 ..., conds: 32

Podemos acceder Nuestro modelo entrenado usando el atributo clf.ruleset_. Un modelo de conjunto de reglas entrenado representa una lista de “y” de “or”:

 >>> clf.ruleset_.out_pretty () 
[[Stalk-surface-above-ring=k^Gill-spacing=c]  V 
[Gill-size=n^Stalk-root=?^Stalk-shape=t]  V 
[Gill-size=n^Population=s]  V 
[Sport-print-color=h^Cap-surface=s]  V 
[Gill-size=n^Cap-surface=s^Stalk-shape=e]  V [19659093] V 
[Population=v^Stalk-shape=e^Bruises?=t]  V 
[Gill-size=n^Stalk-root=b^Gill-spacing=c]  V 
[Gill-size=n^Population=c]  V 
[Gill-size=n^Cap-color=p]  V 
[Gill-size=n^Gill-color=u^Cap-surface=f]  V 
[Gill-size=n^Cap-color=g^Gill-spacing=w]  V 
[Gill-color=g^Stalk-root=b]]

Para generar nuevas predicciones, utilice el método de predicción:

 >>> clf.predict (mysterious_unseen_mushrooms) 
 [VerdaderoFalsoFalsoVerdaderoFalso

También podemos pedir a nuestro modelo que nos diga por qué hizo cada predicción positiva que hizo:

 >>> clf.predicto (mysterious_unseen_mushrooms, give_reasons = True)
 ([TrueFalseFalseTrueFalse
 [[
   ]
  []
  []
 []
  [] ...)

¡Muy bien!

¿Qué tan bien funciona?

Ejecuté las implementaciones de IREP y RIPPER en pruebas repetidas en un conjunto inicial de 11 conjuntos de datos categóricos (en su mayoría de UCI ), utilizando DecisionTreeClassifier de scikit-learn así como grid -Search-optimizado RandomForestClassifier como líneas de base. (Deseché los resultados de dos conjuntos de datos para los cuales sklearn y wittgenstein, respectivamente, se negaron a hacer predicciones positivas).

La comparación de los conjuntos de reglas con una técnica de conjunto como el bosque aleatorio es una comparación un tanto injusta, y el bosque de ajuste de búsqueda de cuadrícula lo hace aún más injusto, pero quería ver qué tan bien wittgenstein podría competir con la mejor alternativa comparable.

Aunque es un clasificador de árbol de decisión, la implementación del árbol de scikit-learn no toma datos categóricos. Pero está bien, solo tendremos que hacer un poco de preprocesamiento para obtener nuestros datos en un formato que DecisionTreeClassifier aceptará.

Primero, utilicemos LabelEncoder de scikit para transformar nuestras características categóricas en numéricas:

 >>> desde sklearn.preprocessing import LabelEncoder 
 >>> le = LabelEncoder () 
 >>> df_le = df.apply (le.fit_transform) 
 >>> df_le.head ()

Entonces, nosotros Utilice una codificación en caliente para crear variables ficticias. De lo contrario, nos quedaremos con características ordinales en lugar de características nominales!

 >>> de sklearn.preprocessing import OneHotEncoder 
 >>> encoder = OneHotEncoder (sparse = False) 
 >>> encoder.fit ( df_le) 
 >>> df_hot = enc.transform (df_le) 
 >>> df_hot.head ()

El preprocesamiento completo, ahora estamos listos para dividir nuestros datos …

 >>> tren , test = train_test_split (df_hot, test_size = .33, ... random_state = random_state) 
 >>> train_X = train [:,n_classes:]
 >>> train_y = train [:,0]
 >>> test_X = test [:,n_classes:]
> >> prueba_y = prueba [:,0]

… y entrena a nuestro modelo:

 >>> tree_clf = DecisionTreeClassifier (random_state = random_state) 
 >>> tree_clf.fit (train_X, train_y)

Para marcar our lovely tree:

 >>> predictions = tree_clf.predict (test_X) 
 >>> precision = precision_score (test_y, predictions) 
 >>> recall = recall_score (test_y, predictions) 
 >>> print (f & # 039; precision: {precision} recordatorio: {recall} node_count: ... {tree_clf.tree_.node_count} & # 039;) 
 ...

Aquí está el código para ajustar y ajustar un bosque aleatorio:

 >>> forest = RandomForestClassifier (random_state = random_state) 
 >>> n_features = train_X.shape [1]
 >>> grid_params = {
 & # 039; n_estimators & # 039 ;: [50,100,200]
 & # 039; max_depth & # 039 ;: [1,3,5,8,10,None]
 & # 039; min_samples_leaf & # 039 ;: [3,6,10,13,16,20]
    } 
 >>> clf = Grid (forest, grid_params, cv = 5) 
 >>> clf.fit (train_X, train_y) 
 >>> best_params = clf.best_params_ 
 >>> forest = RandomForestClassifier ([1945901111]] ... n_estimators = best_params ['n_estimators']
 ... max_depth = best_params ['max_depth']
 ... min_samples_leaf = best_params ['min_samples_leaf']
 ... random_state = random_state) 
 >>> forest .fit (train_X, train_y)

Resultados [19659038] Mi paquete era competitivo con sklearn, al menos en estos conjuntos de datos. (Se puede encontrar un cuaderno Jupyter detallado con las pruebas aquí .)

Aquí hay una comparación de la frecuencia con la que cada modelo de conjunto de reglas venció a cada modelo de sklearn, calificado con precisión:

:

También comparé su compacidad, medido por el número total de condiciones o nodos.

Algunas áreas en las que wittgenstein lo hizo bien:

  • Clases desequilibradas: Ambos algoritmos de conjunto de reglas manejaron clases desequilibradas mejor que basadas en árboles metodos IREP y RIPPER derrotaron a Tree en precisión y recuperación, y ambos lo hicieron sustancialmente mejor que Forest en memoria para conjuntos de datos muy desequilibrados.
  • Overfitting : en conjuntos de datos con menos ejemplos de entrenamiento y menos ejemplos por característica, ambos algoritmos de conjunto de reglas venció a Tree en precisión, y RIPPER venció a Forest. (La ventaja no se extendió a la memoria.)
  • Compacidad / Interpretación: Los modelos IREP y RIPPER eran órdenes de magnitud más compactos que los árboles (listos para usar) y los bosques (sintonizados)

Posibles inconvenientes a considerar:

  • Velocidad: IREP y los árboles de decisión comparten la misma complejidad de tiempo: O (anlogn), donde a es el número de atributos yn, ejemplos. Pero la etapa de optimización de RIPPER puede llevar mucho tiempo en conjuntos de datos más grandes, en O (anlog²n). Más específico para este paquete, hasta que optimizo partes clave de wittgenstein en C ++, RIPPER generalmente toma el más largo de los cuatro en entrenar. IREP, por otro lado, corre bastante rápido, incluso en Python.
  • Características continuas: los árboles de sklearn implementan CART, que utiliza un algoritmo de discretización más sofisticado que el que he implementado hasta ahora. Por ahora, puede obtener un mejor rendimiento de sklearn para conjuntos de datos con muchas características continuas, aunque esto podría cambiar bastante pronto.
  • Rendimiento: Por lo general, esperaría árboles C5.0, bosque aleatorio y parcial los árboles de decisión (un enfoque de conjunto de reglas híbrido) para obtener mejores resultados que los modelos de conjunto de reglas en la mayoría de los conjuntos de datos, pero no en todos; contra los árboles C4.5 y CART, el ganador debería estar más arriba en el aire.

Como con cualquier modelo de aprendizaje automático, sus datos específicos y el problema específico que enfrenta determinan la mejor herramienta para el trabajo. [19659005] Los participantes en el conjunto de reglas son un enfoque de aprendizaje automático que ha sido interesante de implementar y que en algunos casos puede ser útil tener en su conjunto de herramientas.

Me encantaría escuchar sus opiniones, así que no dude en comunicarse con yo en LinkedIn o Github !

Referencias

[1] J. Furnkrantz y G. Widmer, Incremental Reduced Error Pruning (1994, Aprendizaje automático 1994 Actas de la undécima conferencia anual

[2] J. Ross Quinlan, MDL y teorías categóricas (continuación) (1995) Aprendizaje automático 1995 Procedimientos de la duodécima conferencia internacional

[3] W. Cohen, Inducción de regla rápida y efectiva (1995) Aprendizaje automático 19 95 Actas de la Duodécima Conferencia Internacional

[4] E. Frank y IH Witten, Generación de conjuntos de reglas exactas sin optimización global (1998) Aprendizaje automático 1998 Actas de la Duodécima Conferencia Internacional

[5] T. Wang et. al, Un marco bayesiano para el aprendizaje de conjuntos de reglas para la clasificación interpretable (2017) Journal of Machine Learning Research

[6] Ludwig Wittgenstein, Philosophical Investigations (1958)


Cómo para realizar una clasificación explicable de aprendizaje automático: Sin árboles se publicó originalmente en Hacia la ciencia de datos en Medium, donde las personas continúan la conversación destacando y respondiendo a esta historia.