Por Qué Importa la Explicabilidad

Cuando los sistemas de IA toman decisiones de alto riesgo—aprobaciones de préstamos, diagnósticos médicos, recomendaciones de contratación—entender por qué un modelo hizo una predicción específica se vuelve crítico. Marcos regulatorios como el “derecho a explicación” de GDPR y la EU AI (como se discute en AI-Assisted Bug Triaging: Intelligent Defect Prioritization at Scale) Act exigen transparencia.

Testear IA explicable (XAI) (como se discute en AI Code Smell Detection: Finding Problems in Test Automation with ML) valida que las explicaciones son precisas, consistentes y accionables—asegurando que los modelos no solo sean eficientes, sino comprensibles.

Técnicas XAI

1. LIME (Explicaciones Locales Interpretables Model-Agnósticas)

from lime.lime_tabular import LimeTabularExplainer

class (como se discute en [AI Copilot for Test Automation: GitHub Copilot, Amazon CodeWhisperer and the Future of QA](/blog/ai-copilot-testing)) ExplicadorLIME:
    def __init__(self, modelo, datos_entrenamiento, nombres_caracteristicas, nombres_clases):
        self.modelo = modelo
        self.explicador = LimeTabularExplainer(
            training_data=datos_entrenamiento,
            feature_names=nombres_caracteristicas,
            class_names=nombres_clases,
            mode='classification'
        )

    def explicar_prediccion(self, instancia):
        """Generar explicación para instancia única"""
        explicacion = self.explicador.explain_instance(
            instancia,
            self.modelo.predict_proba,
            num_features=10
        )

        return {
            'prediccion': self.modelo.predict([instancia])[0],
            'principales_caracteristicas': explicacion.as_list()
        }

# Uso
explicador = ExplicadorLIME(modelo_prestamo, X_train, nombres_caracteristicas, ['Denegado', 'Aprobado'])

instancia = X_test[0]  # Préstamo denegado
explicacion = explicador.explicar_prediccion(instancia)

print("Predicción:", explicacion['prediccion'])
print("\nPrincipales factores:")
for caracteristica, peso in explicacion['principales_caracteristicas']:
    print(f"  {caracteristica}: {peso:.3f}")

# Salida:
# Predicción: Denegado
# Principales factores:
#   puntuacion_credito <= 650: -0.45
#   deuda_a_ingreso > 0.4: -0.32

2. SHAP (Explicaciones Aditivas SHapley)

import shap

class ExplicadorSHAP:
    def __init__(self, modelo, datos_fondo):
        self.modelo = modelo
        self.explicador = shap.TreeExplainer(modelo, datos_fondo)

    def explicar_instancia(self, instancia):
        """Obtener valores SHAP para predicción única"""
        valores_shap = self.explicador.shap_values(instancia)

        return {
            'valor_base': self.explicador.expected_value,
            'valores_shap': valores_shap,
            'impacto_caracteristica': dict(zip(
                nombres_caracteristicas,
                valores_shap[0] if isinstance(valores_shap, list) else valores_shap
            ))
        }

    def obtener_importancia_global(self, X_test):
        """Importancia global de características"""
        valores_shap = self.explicador.shap_values(X_test)

        # Promedio de valores SHAP absolutos
        shap_abs_promedio = np.abs(valores_shap).mean(axis=0)

        return dict(sorted(
            zip(nombres_caracteristicas, shap_abs_promedio),
            key=lambda x: x[1],
            reverse=True
        ))

# Uso
explicador_shap = ExplicadorSHAP(modelo_xgboost, X_train[:100])

explicacion = explicador_shap.explicar_instancia(X_test[[0]])
print("Impactos de características:")
for caracteristica, impacto in explicacion['impacto_caracteristica'].items():
    print(f"  {caracteristica}: {impacto:+.3f}")

Testeo de Explicabilidad

1. Testing de Consistencia

class TestadorConsistenciaExplicacion:
    def __init__(self, explicador):
        self.explicador = explicador

    def testear_estabilidad(self, instancia, num_ejecuciones=10):
        """Testear si explicaciones son consistentes entre ejecuciones"""
        explicaciones = []

        for _ in range(num_ejecuciones):
            exp = self.explicador.explicar_prediccion(instancia)
            explicaciones.append(exp['principales_caracteristicas'])

        # Calcular varianza en rankings de importancia
        rangos_caracteristicas = {}
        for exp in explicaciones:
            for rango, (caracteristica, peso) in enumerate(exp):
                if caracteristica not in rangos_caracteristicas:
                    rangos_caracteristicas[caracteristica] = []
                rangos_caracteristicas[caracteristica].append(rango)

        # Calcular puntaje de estabilidad
        puntajes_estabilidad = {
            caracteristica: 1 - (np.std(rangos) / len(exp))
            for caracteristica, rangos in rangos_caracteristicas.items()
        }

        estabilidad_promedio = np.mean(list(puntajes_estabilidad.values()))

        return {
            'estabilidad_promedio': estabilidad_promedio,
            'estabilidad_por_caracteristica': puntajes_estabilidad,
            'es_estable': estabilidad_promedio > 0.8
        }

# Uso
testador_consistencia = TestadorConsistenciaExplicacion(explicador_lime)
estabilidad = testador_consistencia.testear_estabilidad(instancia_test)

if not estabilidad['es_estable']:
    print("⚠️ ADVERTENCIA: ¡Explicaciones son inestables!")
    print(f"Estabilidad promedio: {estabilidad['estabilidad_promedio']:.2%}")

2. Testing de Fidelidad

class TestadorFidelidad:
    def __init__(self, modelo, explicador):
        self.modelo = modelo
        self.explicador = explicador

    def testear_ablacion_caracteristicas(self, instancia):
        """Remover características principales, verificar cambio de predicción"""
        # Obtener predicción original
        pred_original = self.modelo.predict_proba([instancia])[0]

        # Obtener explicación
        explicacion = self.explicador.explicar_prediccion(instancia)
        principales_caracteristicas = explicacion['principales_caracteristicas'][:3]

        # Ablacionar características principales
        instancia_ablacionada = instancia.copy()
        for nombre_caracteristica, peso in principales_caracteristicas:
            indice_caracteristica = nombres_caracteristicas.index(nombre_caracteristica)
            instancia_ablacionada[indice_caracteristica] = np.median(X_train[:, indice_caracteristica])

        # Obtener nueva predicción
        pred_ablacionada = self.modelo.predict_proba([instancia_ablacionada])[0]

        # Calcular cambio de predicción
        cambio_pred = abs(pred_original[1] - pred_ablacionada[1])

        return {
            'prediccion_original': pred_original[1],
            'prediccion_ablacionada': pred_ablacionada[1],
            'cambio_prediccion': cambio_pred,
            'es_fiel': cambio_pred > 0.1
        }

Testing de Cumplimiento Regulatorio

Derecho a Explicación GDPR

class TestadorCumplimientoGDPR:
    def testear_adecuacion_explicacion(self, explicacion, prediccion):
        """Verificar que explicación cumple requisitos GDPR"""
        verificaciones = {
            'tiene_caracteristicas_legibles_humano': self.verificar_nombres_caracteristicas(explicacion),
            'proporciona_valores_reales': self.verificar_valores_caracteristicas(explicacion),
            'muestra_direccion_impacto': self.verificar_signos_impacto(explicacion),
            'incluye_confianza': 'confianza' in prediccion,
            'max_caracteristicas_razonable': len(explicacion['principales_caracteristicas']) <= 10
        }

        puntaje_cumplimiento = sum(verificaciones.values()) / len(verificaciones)

        return {
            'es_conforme': puntaje_cumplimiento >= 0.8,
            'puntaje_cumplimiento': puntaje_cumplimiento,
            'verificaciones_fallidas': [k for k, v in verificaciones.items() if not v]
        }

Mejores Prácticas

PrácticaDescripción
Múltiples Métodos de ExplicaciónUsar LIME + SHAP para robustez
Testear EstabilidadVerificar que explicaciones no varíen salvajemente
Validar FidelidadAsegurar que explicaciones reflejen modelo real
Evaluación HumanaExpertos de dominio revisan explicaciones
Ejemplos ContrastivosExplicar diferencias entre instancias similares
Global + LocalProveer insights generales y específicos de instancia

Conclusión

El testing de IA explicable asegura que los modelos no solo sean precisos, sino confiables y conformes. Al testear consistencia, fidelidad y adecuación regulatoria, los equipos construyen sistemas de IA que los humanos pueden entender, depurar y desplegar con confianza.