¿Y si pudiéramos explicar fácilmente modelos excesivamente complejos?

¿Y si pudiéramos simplificar fácilmente modelos excesivamente complejos?

Generar explicaciones contrafácticas se volvió mucho más fácil con CFNOW, pero ¿qué son las explicaciones contrafácticas y cómo puedo usarlas?

Imagen generada con el modelo de difusión de ilusión con texto CFNOW como ilusión (intenta entrecerrar los ojos y mirar desde cierta distancia) | Imagen del autor usando el modelo de difusión estable (licencia)

Este artículo se basa en el siguiente artículo: https://www.sciencedirect.com/science/article/abs/pii/S0377221723006598

Y aquí está la dirección del repositorio de CFNOW: https://github.com/rmazzine/CFNOW

Si estás leyendo esto, puede que sepas lo crucial que se está volviendo la Inteligencia Artificial (IA) en nuestro mundo hoy en día. Sin embargo, es importante tener en cuenta que los enfoques de aprendizaje automático aparentemente efectivos y novedosos, combinados con su popularidad generalizada, pueden llevar a consecuencias imprevistas/deseables.

Esto nos lleva a comprender por qué la Inteligencia Artificial Explicable (XAI, por sus siglas en inglés) es un componente crucial para garantizar el desarrollo ético y responsable de la IA. Esta área demuestra que explicar modelos que consisten en millones o incluso miles de millones de parámetros no es una cuestión trivial. La respuesta a esto es multifacética, ya que existen numerosos métodos que revelan diferentes aspectos del modelo, siendo LIME [1] y SHAP [2] ejemplos populares.

Sin embargo, la complejidad de las explicaciones generadas por estos métodos puede resultar en gráficos o análisis complicados, que potencialmente pueden llevar a interpretaciones erróneas por parte de aquellos que no son expertos bien informados. Una forma posible de evitar esta complejidad es un método simple y natural para explicar las cosas llamado Explicaciones Contrafácticas [3].

Las Explicaciones Contrafácticas aprovechan un comportamiento humano natural para explicar las cosas: crear “mundos alternativos” donde alterar algunos parámetros puede cambiar el resultado. Es una práctica común, probablemente ya hayas hecho algo así — “si tan solo me hubiera despertado un poco más temprano, no habría perdido el autobús” — este tipo de explicación resalta las principales razones de un resultado de manera directa.

Yendo más a fondo, los contrafácticos van más allá de meras explicaciones; pueden servir como guía para realizar cambios, ayudar a depurar comportamientos anómalos y verificar si algunas características pueden modificar las predicciones (sin afectar tanto a la puntuación). Esta naturaleza multifuncional enfatiza la importancia de explicar tus predicciones. No se trata solo de una IA responsable; también es un camino para mejorar los modelos y utilizarlos más allá del alcance de las predicciones. Una característica notable de las explicaciones contrafácticas es su naturaleza impulsada por decisiones, lo que las hace corresponder directamente a un cambio en la predicción [6], a diferencia de LIME y SHAP, que son más adecuados para explicar puntuaciones.

Dados los evidentes beneficios, uno podría preguntarse por qué los contrafácticos no son más populares. ¡Es una pregunta válida! Las barreras principales para la adopción generalizada de las explicaciones contrafácticas son triples [4, 5]: (1) la falta de algoritmos de generación de contrafácticos compatibles y fáciles de usar, (2) la ineficiencia de los algoritmos para generar contrafácticos y (3) la falta de representación visual integral.

Pero ¡tengo buenas noticias para ti! Un nuevo paquete, CFNOW (CounterFactuals NOW o CounterFactual Nearest Optimal Wololo), está a la altura de enfrentar estos desafíos. CFNOW es un versátil paquete de Python capaz de generar múltiples contrafácticos para varios tipos de datos, como datos tabulares, imágenes y entradas de texto (incrustaciones). Adopta un enfoque agnóstico con respecto al modelo, requiriendo solo datos mínimos — (1) el punto fáctico (punto a ser explicado) y (2) la función de predicción.

Además, CFNOW se estructura para permitir el desarrollo e integración de nuevas estrategias para encontrar y ajustar contrafácticos basados en lógica personalizada. También cuenta con CounterPlots, una estrategia novedosa para representar visualmente las explicaciones contrafácticas.

El núcleo de CFNOW es un marco que convierte los datos en una estructura única manejable por el generador CF. Siguiendo esto, se lleva a cabo un proceso de dos pasos que localiza y optimiza el contrafáctico encontrado. Para evitar mínimos locales, el paquete implementa la Búsqueda Tabú, un método matheurístico que le permite explorar nuevas regiones donde la función objetivo podría ser mejor optimizada.

Las secciones siguientes de este texto se centrarán en demostrar cómo CFNOW puede ser utilizado eficientemente para generar explicaciones para clasificadores tabulares, de imágenes y de texto (incrustación).

Clasificadores Tabulares

Aquí, mostramos lo usual, tienes datos tabulares con múltiples tipos de datos. En el ejemplo que se muestra a continuación, utilizaré un conjunto de datos que tiene datos numéricos continuos, datos categóricos binarios y datos codificados en caliente categóricos para mostrar todo el poder de CFNOW.

Lo primero es lo primero, debes instalar el paquete CFNOW, el requisito es una versión de Python superior a 3.8:

    pip install cfnow

(aquí está el código completo para este ejemplo: https://colab.research.google.com/drive/1GUsVfcM3I6SpYCmsBAsKMsjVdm-a6iY6?usp=sharing)

En esta primera parte, crearemos un clasificador con el conjunto de datos de Adultos. Luego, no hay muchas novedades aquí:

import warningsimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import accuracy_scorewarnings.filterwarnings("ignore", message="X does not have valid feature names, but RandomForestClassifier was fitted with feature names")

Importamos paquetes básicos para crear el modelo de clasificación y también desactivamos las advertencias relacionadas con hacer predicciones sin los nombres de las columnas.

Luego, procedemos a escribir el clasificador donde la clase 1 representa un ingreso menor o igual a 50k (<=50K) y la clase 0 representa un alto ingreso.

# Crear el clasificadorimport warningsimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import accuracy_scorewarnings.filterwarnings("ignore", message="X does not have valid feature names, but RandomForestClassifier was fitted with feature names")# Cargar el conjunto de datos Adultdataset_url = "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data"column_names = ['age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status',                'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss',                'hours-per-week', 'native-country', 'income']data = pd.read_csv(dataset_url, names=column_names, na_values=" ?", skipinitialspace=True)# Eliminar filas con valores faltantesdata = data.dropna()# Identificar las características categóricas que no son binariasnon_binary_categoricals = [column for column in data.select_dtypes(include=['object']).columns                            if len(data[column].unique()) > 2]binary_categoricals = [column for column in data.select_dtypes(include=['object']).columns                        if len(data[column].unique()) == 2]cols_numericals = [column for column in data.select_dtypes(include=['int64']).columns]# Aplicar codificación one-hot a las características categóricas no binariasdata = pd.get_dummies(data, columns=non_binary_categoricals)# Convertir las características categóricas binarias en números# Esto también binarizará la variable objetivo (ingreso)for bc in binary_categoricals:    data[bc] = data[bc].apply(lambda x: 1 if x == data[bc].unique()[0] else 0)# Dividir el conjunto de datos en características y variable objetivaX = data.drop('income', axis=1)y = data['income']# Dividir el conjunto de datos en conjuntos de entrenamiento y pruebax_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)# Inicializar un RandomForestClassifierclf = RandomForestClassifier(random_state=42)# Entrenar el clasificadorclf.fit(x_train, y_train)# Realizar predicciones sobre el conjunto de pruebasy_pred = clf.predict(x_test)# Evaluar el clasificadorprecisión = accuracy_score(y_test, y_pred)print("Precisión:", precisión)

Con el código anterior, creamos un conjunto de datos, lo preprocesamos, creamos un modelo de clasificación, realizamos una predicción y evaluamos sobre el conjunto de pruebas.

Ahora, tomemos un punto (el primero del conjunto de pruebas) y verifiquemos su predicción:

clf.predict([X_test.iloc[0]])# Result: 0 -> Alto ingreso

Ahora es el momento de usar CFNOW para calcular cómo podemos cambiar esta predicción modificando mínimamente las características:

from cfnow import find_tabular# Luego, usamos CFNOW para generar la modificación mínima para cambiar la clasificacióncf_res = find_tabular(    factual=X_test.iloc[0],    feat_types={c: 'num' if c in cols_numericals else 'cat' for c in X.columns},    has_ohe=True,    model_predict_proba=clf.predict_proba,    limit_seconds=60)

El código anterior:

  • factualAgrega la instancia factual como pd.Series
  • feat_typesEspecifica los tipos de características (“num” para continuas numéricas y “cat” para categóricas)
  • has_oheIndica que tenemos características OHE (detecta automáticamente las características OHE agregando aquellas que tienen el mismo prefijo seguido de un guion bajo, por ejemplo, country_brazil, country_usa, country_ireland).
  • model_predict_probaIncluye una función de predicción
  • limit_secondsDefine un umbral de tiempo total para la ejecución, esto es importante porque la etapa de ajuste fino puede continuar indefinidamente (el valor predeterminado es de 120 segundos)

Luego, después de algún tiempo, podemos evaluar la clase del mejor contrafáctico (primer índice de cf_res.cfs)

clf.predict([cf_obj.cfs[0]])# Resultado: 1 -> Bajo ingreso

Y aquí vienen algunas diferencias con CFNOW, ya que también integra CounterPlots, podemos trazar sus gráficos y obtener información más perspicaz como la siguiente:

Gráfico CounterShapley para nuestro CF | Imagen del autor

El gráfico CounterShapley a continuación muestra la importancia relativa de cada característica para generar la predicción contrafáctica. Aquí, tenemos algunas ideas interesantes que muestran que el estado civil (si está combinado) representa más del 50% de la contribución a la clase CF.

Gráfico Greedy para nuestro CF | Imagen del autor

El gráfico Greedy muestra algo muy similar al CounterShapley, la principal diferencia aquí es la secuencia de cambios. Mientras que el CounterShapley no considera ninguna secuencia específica (calculando contribuciones usando valores de Shapley), el gráfico Greedy utiliza la estrategia más codiciosa para modificar la instancia factual, cada paso cambia la característica que más contribuye a la clase CF. Esto puede ser útil en situaciones donde se proporciona alguna orientación de manera codiciosa (cada paso eligiendo el mejor enfoque para lograr el objetivo).

Gráfico Constellation para nuestro CF | Imagen del autor

Finalmente, tenemos el análisis más complejo, el gráfico Constellation. A pesar de su aspecto intimidante, es bastante fácil de interpretar. Cada punto grande rojo representa un cambio único en la característica (respecto a la etiqueta), y los puntos más pequeños representan la combinación de dos o más características. Por último, el punto azul grande representa la puntuación CF. Aquí, podemos ver que la única forma de obtener un CF con estas características es modificando todas ellas a sus valores respectivos (es decir, no hay un subconjunto que genere un CF). También podemos profundizar e investigar la relación entre las características y encontrar patrones interesantes.

En este caso particular, fue interesante observar que una predicción de alto ingreso cambiaría si la persona fuera mujer, divorciada y con un hijo propio. Este contrafáctico puede conducir a discusiones más profundas sobre los impactos económicos en diferentes grupos sociales.

Clasificadores de imágenes

Como ya se mencionó, CFNOW puede trabajar con diversos tipos de datos, por lo que también puede generar contrafactuales para datos de imágenes. Sin embargo, ¿qué significa tener un contrafactual para un conjunto de datos de imágenes?

La respuesta puede variar porque hay varias formas en las que se pueden generar contrafactuales. Puede ser reemplazar píxeles individuales con ruido aleatorio (un método utilizado por ataques adversarios) o algo más complejo, que involucra métodos avanzados de segmentación.

CFNOW utiliza un método de segmentación llamado quickshift, que es un método confiable y rápido para detectar segmentos “semánticos”. Sin embargo, es posible integrar (y te invito a hacerlo) otras técnicas de segmentación.

La detección de segmentos por sí sola no es suficiente para generar explicaciones contrafactuales. También necesitamos modificar los segmentos, reemplazándolos con versiones modificadas. Para esta modificación, CFNOW tiene cuatro opciones definidas en el parámetro replace_mode, donde podemos tener: (default) blur — que agrega un filtro de desenfoque a los segmentos reemplazados, mean que reemplaza los segmentos por el color promedio, random que los reemplaza con ruido aleatorio, y inpaint, que reconstruye la imagen en base a los píxeles vecinos.

Si quieres ver todo el código, puedes encontrarlo aquí: https://colab.research.google.com/drive/1M6bEP4x7ilSdh01Gs8xzgMMX7Uuum5jZ?usp=sharing

A continuación, mostraré la implementación del código de CFNOW para este tipo de datos:

En primer lugar, nuevamente, debemos instalar el paquete CFNOW si aún no lo has hecho.

pip install cfnow

Ahora, agreguemos algunos paquetes adicionales para cargar un modelo preentrenado:

pip install torch torchvision Pillow requests

Luego, carguemos los datos, carguemos el modelo preentrenado y creemos una función de predicción que sea compatible con el formato de datos que CFNOW debe recibir:

import requestsimport numpy as npfrom PIL import Imagefrom torchvision import models, transformsimport torch# Cargar un modelo ResNet preentrenadomodel = models.resnet50(pretrained=True)model.eval()# Definir la transformación de la imagentransform = transforms.Compose([transform.Resize(256),transform.CenterCrop(224),transform.ToTensor(),transform.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),])# Obtener una imagen desde la weburl_imagen = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Sunflower_from_Silesia2.jpg/320px-Sunflower_from_Silesia2.jpg"respuesta = requests.get(url_imagen, stream=True)imagen = np.array(Image.open(respuesta.raw))def predecir(imagenes):if len(np.shape(imagenes)) == 4:# Convertir la lista de matrices numpy a un lote de tensoresimagenes_entrada = torch.stack([transform(Image.fromarray(imagen.astype('uint8'))) for imagen in imagenes])elif len(np.shape(imagenes)) == 3:imagenes_entrada = transform(Image.fromarray(imagenes.astype('uint8')))else:raise ValueError("La entrada debe ser una lista de imágenes o una sola imagen.")# Verificar si hay una GPU disponible y si no, utilizar una CPUdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")imagenes_entrada = imagenes_entrada.to(device)model.to(device)# Realizar la inferenciacon torch.no_grad():salidas = model(imagenes_entrada)# Devolver un arreglo de puntuaciones de predicción para cada imagenreturn torch.asarray(salidas).cpu().numpy()URL_ETIQUETAS = "https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json"def predecir_etiqueta(salidas):# Cargar las etiquetas utilizadas por el modelo preentrenadolabels = requests.get(URL_ETIQUETAS).json()# Obtener las etiquetas predichaspredicted_idxs = [np.argmax(salida) for salida in salidas]predicted_labels = [labels[idx.item()] for idx in predicted_idxs]return predicted_labels# Verificar la predicción para la imagenetiqueta_predicha = predecir([np.array(imagen)])print("Etiquetas predichas:", predecir_etiqueta(etiqueta_predicha))

La mayor parte del trabajo de código está relacionado con la construcción del modelo, obtención de los datos y ajuste de estos, porque para generar contrafactuales con CFNOW solo necesitamos:

from cfnow import find_imagecf_img = find_image(img=image, model_predict=predict)cf_img_hl = cf_img.cfs[0]print("Etiquetas predichas:", predict_label(predict([cf_img_hl])))# Mostrar la imagen CFImage.fromarray(cf_img_hl.astype('uint8'))

En el ejemplo anterior, utilizamos todos los parámetros opcionales por defecto, por lo tanto, usamos quickshift para segmentar la imagen y reemplazar los segmentos con imágenes borrosas. Como resultado, tenemos esta predicción factual a continuación:

Imagen factual clasificada como una “margarita” | Título de la imagen: Girasol (Helianthus L). Słonecznik por Pudelek (Editado por Yzmo y Vassil) de Wikimedia bajo la Licencia de Documentación Libre de GNU, Versión 1.2

A continuación, tenemos lo siguiente:

Imagen CF clasificada como una “abeja” | Título de la imagen: Girasol (Helianthus L). Słonecznik por Pudelek (Editado por Yzmo y Vassil) de Wikimedia bajo la Licencia de Documentación Libre de GNU, Versión 1.2

Entonces, ¿cuáles son los resultados de este análisis? En realidad, los contrafactuales de imágenes pueden ser herramientas extremadamente útiles para detectar cómo el modelo está clasificando. Esto se puede aplicar en casos donde: (1) queremos verificar por qué el modelo hizo clasificaciones correctas, asegurándonos de que está utilizando características de imagen correctas: en este caso, aunque clasificó erróneamente el girasol como una margarita, podemos ver que al difuminar la flor (y no una característica del fondo) se produce un cambio en la predicción. También puede (2) ayudar a diagnosticar imágenes clasificadas incorrectamente, lo cual puede brindar mejores ideas para el procesamiento de imágenes y/o la adquisición de datos.

Clasificadores Textuales

Finalmente, tenemos clasificadores textuales basados en embeddings. Aunque los clasificadores textuales simples (que utilizan una estructura de datos más similar a datos tabulares) pueden usar el generador de contrafactuales tabulares, esto no está tan claro para los clasificadores textuales basados en embeddings.

La justificación es que los embeddings tienen un número variable de entradas y palabras que pueden afectar considerablemente la puntuación de predicción y la clasificación.

CFNOW soluciona esto con dos estrategias: (1) eliminando evidencia o (2) agregando antónimos. La primera estrategia es sencilla, para medir el impacto de cada palabra en el texto, simplemente las eliminamos y observamos cuáles debemos eliminar para invertir la clasificación. Mientras que, al agregar antónimos, posiblemente se puede mantener una estructura semántica (porque eliminar una palabra puede dañarla severamente).

A continuación, el siguiente código muestra cómo usar CFNOW en este contexto.

Si deseas ver todo el código, puedes consultarlo aquí: https://colab.research.google.com/drive/1ZMbqJmJoBukqRJGqhUaPjFFRpWlujpsi?usp=sharing

Primero, instala el paquete CFNOW:

pip install cfnow

Luego, instala los paquetes necesarios para la clasificación textual:

pip install transformers

Luego, como en las secciones anteriores, primero construiremos el clasificador:

from transformers import DistilBertTokenizer, DistilBertForSequenceClassificationfrom transformers import pipelineimport numpy as np# Carga el modelo pre-entrenado y el tokenizador para el análisis de sentimientosmodel_name = "distilbert-base-uncased-finetuned-sst-2-english"tokenizer = DistilBertTokenizer.from_pretrained(model_name)model = DistilBertForSequenceClassification.from_pretrained(model_name)# Define el pipeline de análisis de sentimientossentiment_analysis = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)# Define un conjunto de datos simpletext_factual = "Me gustó esta película porque fue divertida, pero a mis amigos no les gustó porque fue demasiado larga y aburrida."result = sentiment_analysis(text_factual)print(f"{text_factual}: {result[0]['label']} (confianza: {result[0]['score']:.2f})")        def pred_score_text(list_text):    if type(list_text) == str:        sa_pred = sentiment_analysis(list_text)[0]        sa_score = sa_pred['score']        sa_label = sa_pred['label']        return sa_score if sa_label == "POSITIVE" else 1.0 - sa_score    return np.array([sa["score"] if sa["label"] == "POSITIVE" else 1.0 - sa["score"] for sa in sentiment_analysis(list_text)])

Para este código, veremos que nuestro texto factual tiene un sentimiento NEGATIVO con una alta confianza (≥0.9), luego intentemos generar el contrafáctico:

from cfnow import find_textcf_text = find_text(text_input=text_factual, textual_classifier=pred_score_text)result_cf = sentiment_analysis(cf_text.cfs[0])print(f"CF: {cf_text.cfs[0]}: {result_cf[0]['label']} (confianza: {result_cf[0]['score']:.2f})")

Con el código anterior, al cambiar una sola palabra (but), la clasificación cambió de NEGATIVO a POSITIVO con alta confianza. Esto muestra cómo los contrafácticos pueden ser útiles, ya que estas modificaciones mínimas pueden tener implicaciones en la comprensión de cómo el modelo predice oraciones y/o ayudar a depurar comportamientos indeseables.

Conclusión

Esta fue una introducción (relativamente) breve a CFNOW y las explicaciones contrafácticas. Existe una amplia y creciente literatura sobre contrafácticos que definitivamente deberías revisar si deseas profundizar, este artículo seminal [3] escrito por (mi asesor de doctorado, Prof. David Martens) es una excelente manera de tener una mejor introducción a las Explicaciones Contrafácticas. Además, hay buenas revisiones como esta escrita por Verma et al [7]. En resumen, las explicaciones contrafácticas son una forma fácil y conveniente de explicar las decisiones de algoritmos de aprendizaje automático complejos, y pueden hacer mucho más que explicaciones si se aplican correctamente. CFNOW puede proporcionar una forma fácil, rápida y flexible de generar explicaciones contrafácticas, permitiendo a los profesionales no solo explicar, sino también aprovechar al máximo el potencial de sus datos y modelo.

Referencias:

[1] – https://github.com/marcotcr/lime[2] – https://github.com/shap/shap[3] – https://www.jstor.org/stable/26554869[4] – https://www.mdpi.com/2076-3417/11/16/7274[5] – https://arxiv.org/pdf/2306.06506.pdf[6] – https://arxiv.org/abs/2001.07417[7] – https://arxiv.org/abs/2010.10596

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Ciencia de Datos

La guía de campo de datos sintéticos

Si quieres trabajar con datos, ¿cuáles son tus opciones? Aquí tienes una respuesta lo más general posible podrías obt...

Inteligencia Artificial

Cómo introducir computadoras cuánticas sin frenar el crecimiento económico

Para allanar el camino de la revolución cuántica, los investigadores y los gobiernos deben predecir y prepararse para...

Inteligencia Artificial

Desbloquea el avance de la comprensión de video de IA con MM-VID para GPT-4V(isión)

En todo el mundo, las personas crean una gran cantidad de videos todos los días, incluyendo transmisiones en vivo gen...

Inteligencia Artificial

Aterrizaje de Chandrayaan 3 Cómo la IA y los sensores ayudaron en la épica empresa lunar de la ISRO.

En la fascinante expansión de la exploración espacial, cada misión es una apuesta cósmica, cada una un lanzamiento de...

Inteligencia Artificial

Molino de Turing la supercomputadora de IA impulsa el motor económico del Reino Unido

El hogar de la primera revolución industrial acaba de hacer una inversión masiva en la próxima. El gobierno del Reino...