La explicabilidad en la IA es esencial para ganar confianza en las predicciones del modelo y es muy importante para mejorar la solidez del modelo. Una buena explicabilidad a menudo actúa como una herramienta de depuración, revelando fallas en el proceso de entrenamiento del modelo. Si bien los Valores Shapley se han convertido en el estándar de la industria para esta tarea, debemos preguntarnos: ¿siempre funcionan? Y, sobre todo, ¿en qué fallan?
Para comprender dónde fallan los valores de Shapley, el mejor enfoque es controlar la verdad fundamental. Comenzaremos con un modelo lineal simple y luego desglosaremos sistemáticamente la explicación. Al observar cómo reaccionan los valores de Shapley a estos cambios controlados, podemos identificar con precisión dónde producen resultados engañosos y cómo solucionarlos.
El modelo de juguete
Comenzaremos con un modelo con 100 variables aleatorias uniformes.
importar numpy como np desde sklearn.linear_model importar LinearRegression importar shap def get_shapley_values_linear_independent_variables( pesos: np.ndarray, datos: np.ndarray ) -> np.ndarray: devolver pesos * datos # Top compare los resultados teóricos con el paquete shap def get_shap(pesos: np.ndarray, datos: np.ndarray): modelo = LinearRegression() model.coef_ = pesos # Inyecta tus pesos model.intercept_ = 0 fondo = np.zeros((1, pesos.forma[0])) explicador = shap.LinearExplainer(modelo, fondo) # Asume independencia entre todas las características resultados = explicador.shap_values(datos) devuelve resultados DIM_SPACE = 100 np.random.seed(42) # Genera pesos aleatorios y pesos de datos = np.random.rand(DIM_SPACE) datos = np.random.rand(1, DIM_SPACE) # Establece valores específicos para poner a prueba nuestra intuición # Característica 0: Peso alto (10), Característica 1: Peso cero[0] = 10 pesos[1] = 0 # Establecer el valor máximo para los dos primeros datos de características[0, 0:2] = 1 shap_res = get_shapley_values_linear_independent_variables(pesos, datos) shap_res_pacakge = get_shap(pesos, datos) idx_max = shap_res.argmax() idx_min = shap_res.argmin() print( f”Esperado: idx_max 0, idx_min 1\nActual: idx_max {idx_max}, idx_min: {idx_min}” ) print(abs(shap_res_pacakge – shap_res).max()) # No hay diferencia
En este sencillo ejemplo, donde todas las variables son independientes, el cálculo se simplifica drásticamente.
Recuerde que la fórmula de Shapley se basa en la contribución marginal de cada característica, la diferencia en el resultado del modelo cuando se agrega una variable a una coalición de características conocidas versus cuando está ausente.
\[ V(S∪{i}) – V(S)
\]
Dado que las variables son independientes, la combinación específica de características preseleccionadas (S) no influye en la contribución de la característica i. El efecto de las características preseleccionadas y no seleccionadas se cancela entre sí durante la resta, sin tener impacto en la influencia de la característica i. Por lo tanto, el cálculo se reduce a medir el efecto marginal de la característica i directamente en el resultado del modelo:
\[ W_i · X_i \]
El resultado es intuitivo y funciona como se esperaba. Como no hay interferencia de otras funciones, la contribución depende únicamente del peso de la función y su valor actual. En consecuencia, la característica con la mayor combinación de peso y valor es la característica que más contribuye. En nuestro caso, el índice de característica 0 tiene un peso de 10 y un valor de 1.
Rompamos las cosas
Ahora, introduciremos dependencias para ver dónde empiezan a fallar los valores de Shapley.
En este escenario, induciremos artificialmente una correlación perfecta duplicando la característica más influyente (índice 0) 100 veces. Esto da como resultado un nuevo modelo con 200 funciones, donde 100 funciones son copias idénticas de nuestro colaborador principal original e independientes del resto de las 99 funciones. Para completar la configuración, asignamos un peso cero a todas estas funciones duplicadas agregadas. Esto garantiza que las predicciones del modelo permanezcan sin cambios. Solo estamos alterando la estructura de los datos de entrada, no la salida. Si bien esta configuración parece extrema, refleja un escenario común del mundo real: tomar una señal importante conocida y crear múltiples características derivadas (como promedios móviles, retrasos o transformaciones matemáticas) para capturar mejor su información.
Sin embargo, debido a que la Característica 0 original y sus nuevas copias son perfectamente dependientes, el cálculo de Shapley cambia.
Basado en el axioma de simetría: si dos características contribuyen igualmente al modelo (en este caso, al llevar la misma información), deben recibir el mismo crédito.
Intuitivamente, conocer el valor de cualquier clon revela la información completa del grupo. Como resultado, la contribución masiva que vimos anteriormente para la función única ahora se divide en partes iguales entre ella y sus 100 clones. La “señal” se diluye, haciendo que el impulsor principal del modelo parezca mucho menos importante de lo que realmente es.
Aquí está el código correspondiente:
importar numpy como np desde sklearn.linear_model importar LinearRegression importar shap def get_shapley_values_linear_correlacionado (pesos: np.ndarray, datos: np.ndarray) -> np.ndarray: res = pesos * datos duplicados_índices = np.array (
[0] + lista(rango(datos.forma[1] – DUPLICATE_FACTOR, forma.datos[1])) ) # sumaremos esas contribuciones y dividiremos la contribución entre ellas full_contrib = np.sum(res[:, duplicated_indices]eje = 1) duplicado_característica_factor = np.ones (datos.forma[1]) factor_característica_duplicada[duplicated_indices] = 1 / (DUPLICATE_FACTOR + 1) full_contrib = np.tile(full_contrib, (DUPLICATE_FACTOR+1, 1)).T res[:, duplicated_indices] = full_contrib res *= duplicado_feature_factor return res def get_shap(weights: np.ndarray, data: np.ndarray): model = LinearRegression() model.coef_ =weights # Inyecta tus pesos model.intercept_ = 0 explicador = shap.LinearExplainer(model, data, feature_perturbation=”correlation_dependent”) resultados = explicador.shap_values(data) devuelve resultados DIM_SPACE = 100 DUPLICATE_FACTOR = 100 np.random.seed(42) pesos = np.random.rand(DIM_SPACE) pesos[0] = 10 pesos[1] = 0 datos = np.random.rand(10000, DIM_SPACE) datos[0, 0:2] = 1 # Copia duplicada de la característica 0, 100 veces: dup_data = np.tile(data[:, 0](DUPLICATE_FACTOR, 1)).T data = np.concatenate((data, dup_data), axis=1) # Pondremos peso cero para todas esas características agregadas: pesos = np.concatenate((weights, np.tile(0, (DUPLICATE_FACTOR)))) shap_res = get_shapley_values_linear_cor related(weights, data) shap_res = forma_res[0, :] # Tomar el primer registro para probar los resultados idx_max = shap_res.argmax() idx_min = shap_res.argmin() print(f”Esperado: idx_max 0, idx_min 1\nActual: idx_max {idx_max}, idx_min: {idx_min}”)
Claramente esto no es lo que pretendíamos y no proporciona una buena explicación para modelar el comportamiento. Idealmente, queremos que la explicación refleje la verdad fundamental: la característica 0 es el controlador principal (con un peso de 10), mientras que las características duplicadas (índices 101 a 200) son simplemente copias redundantes con peso cero. En lugar de diluir la señal en todas las copias, claramente preferiríamos una atribución que resalte la verdadera fuente de la señal.
Nota: Si ejecuta esto usando el paquete shap de Python, puede notar que los resultados son similares pero no idénticos a nuestro cálculo manual. Esto se debe a que calcular los valores de Shapley es computacionalmente inviable. Por lo tanto, bibliotecas como shap se basan en métodos de aproximación que introducen ligeramente la variación.
¿Podemos arreglar esto?
Dado que la correlación y las dependencias entre características son extremadamente comunes, no podemos ignorar este problema.
Por un lado, los valores de Shapley sí dan cuenta de estas dependencias. Una característica con un coeficiente de 0 en un modelo lineal y sin efecto directo en la salida recibe una contribución distinta de cero porque contiene información compartida con otras características. Sin embargo, este comportamiento, impulsado por el axioma de simetría, no siempre es lo que queremos para una explicabilidad práctica. Si bien dividir “justamente” el crédito entre características correlacionadas es matemáticamente sensato, a menudo oculta los verdaderos impulsores del modelo.
Varias técnicas pueden manejar esto y las exploraremos.
Funciones de agrupación
Este enfoque es particularmente crítico para modelos de espacio de características de alta dimensión, donde la correlación de características es inevitable. En estos entornos, intentar atribuir contribuciones específicas a cada variable resulta a menudo ruidoso y computacionalmente inestable. En cambio, podemos agregar características similares que representan el mismo concepto en un solo grupo. Una analogía útil proviene de la clasificación de imágenes: si queremos explicar por qué un modelo predice “gato” en lugar de “perro”, examinar píxeles individuales no tiene sentido. Sin embargo, si agrupamos los píxeles en “parches” (por ejemplo, orejas, cola), la explicación se vuelve inmediatamente interpretable. Al aplicar esta misma lógica a los datos tabulares, podemos calcular la contribución del grupo en lugar de dividirla arbitrariamente entre sus componentes.
Esto se puede lograr de dos maneras: simplemente sumando los valores de Shapley dentro de cada grupo o calculando directamente la contribución del grupo. En el método directo, tratamos al grupo como una entidad única. En lugar de alternar funciones individuales, tratamos la presencia y ausencia del grupo como presencia o ausencia simultánea de todas las funciones dentro de él. Esto reduce la dimensionalidad del problema, haciendo que la estimación sea más rápida, más precisa y más estable.
El ganador se lo lleva todo
Si bien la agrupación es eficaz, tiene limitaciones. Requiere definir los grupos de antemano y a menudo ignora las correlaciones entre esos grupos.
Esto conduce a una “redundancia de explicaciones”. Volviendo a nuestro ejemplo, si las 101 funciones clonadas no están preagrupadas, el resultado repetirá esas 101 funciones con la misma contribución 101 veces. Esto es abrumador, repetitivo y funcionalmente inútil. La explicabilidad efectiva debería reducir la redundancia y mostrar algo nuevo al usuario cada vez.
Para lograr esto, podemos crear un proceso iterativo codicioso. En lugar de calcular todos los valores a la vez, podemos seleccionar características paso a paso:
Seleccione el “Ganador”: Identifique la característica individual (o grupo) con la contribución individual más alta. Condicione el siguiente paso: Vuelva a evaluar las características restantes, suponiendo que las características del paso anterior ya se conocen. Los incorporaremos en el subconjunto de características preseleccionadas S en el valor de Shapley cada vez. Repita: Pregúntele al modelo: “Dado que el usuario ya conoce las características A, B, C, ¿qué característica restante aporta más información?”
Al recalcular los valores de Shapley (o contribuciones marginales) condicionados a las características preseleccionadas, nos aseguramos de que las características redundantes caigan efectivamente a cero. Si la Función A y la Función B son idénticas y se selecciona primero la Función A, la Función B ya no proporciona información nueva. Se filtra automáticamente, dejando una lista limpia y concisa de distintos controladores.
Nota: Puede encontrar una implementación de este grupo directo y cálculo iterativo codicioso en nuestro paquete Python medpython.
Divulgación completa: soy coautor de este paquete de código abierto.
Validación del mundo real
Si bien este modelo de juguete demuestra fallas matemáticas en el método de valores de Shapley, ¿cómo funciona en escenarios de la vida real?
Aplicamos esos métodos de Grouped Shapley con Winner se lo lleva todo, además de más métodos (que están fuera del alcance de esta publicación, tal vez la próxima vez), en entornos clínicos complejos utilizados en la atención médica. Nuestros modelos utilizan cientos de características con fuerte correlación que se agruparon en docenas de conceptos.
Este método se validó en varios modelos en un entorno ciego cuando nuestros médicos no sabían qué método estaban inspeccionando y superaron los valores básicos de Shapley en sus clasificaciones. Cada técnica contribuyó por encima del experimento anterior en un experimento de varios pasos. Además, nuestro equipo utilizó estas mejoras de explicabilidad como parte de nuestra presentación al CMS Health AI Challenge, donde fuimos seleccionados como ganadores del premio.
Conclusión
Los valores de Shapley son el estándar de oro para la explicabilidad del modelo, proporcionando una forma matemáticamente rigurosa de atribuir crédito.
Sin embargo, como hemos visto, la “corrección” matemática no siempre se traduce en una explicabilidad efectiva.
Cuando las características están altamente correlacionadas, la señal puede diluirse, ocultando los verdaderos controladores de su modelo detrás de un muro de redundancia.
Exploramos dos formas de solucionar este problema:
Agrupación: agregar características en un solo concepto. Selección iterativa: condicionar conceptos ya presentados para extraer solo información nueva, eliminando efectivamente la redundancia.
Al reconocer estas limitaciones, podemos asegurarnos de que nuestras explicaciones sean significativas y útiles.
Si esto le resultó útil, conectémonos en LinkedIn