27 votos

¿Por qué el LDA de scikit-learn de Python no funciona correctamente y cómo calcula el LDA mediante SVD?

Estaba utilizando el Análisis Discriminante Lineal (LDA) del scikit-learn biblioteca de aprendizaje automático (Python) para la reducción de la dimensionalidad y tenía un poco de curiosidad por los resultados. Ahora me pregunto qué es el LDA en scikit-learn está haciendo para que los resultados se vean diferentes de, por ejemplo, un enfoque manual o un LDA hecho en R. Sería genial si alguien pudiera darme algunas ideas aquí.

Lo más preocupante es que la scikit-plot muestra una correlación entre las dos variables donde debería haber una correlación 0.

Para hacer una prueba, utilicé el conjunto de datos Iris y los 2 primeros discriminantes lineales tenían este aspecto:

IMG-1. LDA mediante scikit-learn

enter image description here

Esto es básicamente consistente con los resultados que encontré en el scikit-learn la documentación aquí.

Ahora, he revisado el LDA paso a paso y he obtenido una proyección diferente. Probé diferentes enfoques para averiguar qué estaba pasando:

IMG-2. LDA en datos brutos (sin centrado, sin normalización)

enter image description here

Y aquí estaría el paso a paso si estandarizara (normalización de la puntuación z; varianza unitaria) los datos primero. Hice lo mismo sólo con el centrado de la media, lo que debería conducir a la misma imagen de proyección relativa (y que de hecho lo hizo).

IMG-3. LDA paso a paso tras el centrado de la media, o estandarización

enter image description here

IMG-4. LDA en R (configuración por defecto)

LDA en IMG-3 donde centré los datos (que sería el enfoque preferido) también se ve exactamente igual que el que encontré en un Post de alguien que hizo el LDA en R enter image description here


Código de referencia

No quería pegar todo el código aquí, pero lo he subido como un Cuaderno IPython aquí desglosado en los diversos pasos que utilicé (ver más abajo) para la proyección LDA.

  1. Paso 1: Cálculo de los vectores medios d-dimensionales $$\mathbf m_i = \frac{1}{n_i} \sum\limits_{\mathbf x \in D_i}^n \; \mathbf x_k$$

  2. Paso 2: Cálculo de las matrices de dispersión

    2.1 La matriz de dispersión dentro de la clase $S_W$ se calcula mediante la siguiente ecuación:
    $$S_W = \sum\limits_{i=1}^{c} S_i = \sum\limits_{i=1}^{c} \sum\limits_{\mathbf x \in D_i}^n (\mathbf x - \mathbf m_i)\;(\mathbf x - \mathbf m_i)^T$$

    2.2 La matriz de dispersión entre clases $S_B$ se calcula mediante la siguiente ecuación:
    $$S_B = \sum\limits_{i=1}^{c} n_i (\mathbf m_i - \mathbf m) (\mathbf m_i - \mathbf m)^T$$ donde $\mathbf m$ es la media global.

  3. Paso 3. Resolver el problema de valores propios generalizado para la matriz $S_{W}^{-1}S_B$

    3.1. Clasificación de los vectores propios por valores propios decrecientes

    3.2. Elección de k vectores propios con los mayores valores propios. Combinando los dos vectores propios con los valores propios más altos para construir nuestro $d \times k$ -matriz eigenvectorial de dimensiones $\mathbf W$

  4. Paso 5: Transformación de las muestras en el nuevo subespacio $$\mathbf y = \mathbf W^T \times \mathbf x.$$

0 votos

No he repasado para buscar las diferencias, pero puedes ver exactamente lo que hace scikit-learn en la fuente .

0 votos

Parece que también están estandarizando (centrando y luego escalando mediante la división por la desviación estándar). Esto, yo esperaría un resultado similar al de mi tercer gráfico (y el de R)...hmm

0 votos

Raro: el gráfico que has obtenido con scikit (y el que muestran en su documentación) no tiene sentido. El LDA siempre arroja proyecciones que tienen correlación cero, pero obviamente hay un muy fuerte correlación entre las proyecciones de scikit en los ejes discriminantes 1 y 2. Algo está claramente mal ahí.

21voto

zowens Puntos 1417

Actualización: Gracias a esta discusión, scikit-learn se actualizó y ahora funciona correctamente. Su código fuente LDA se puede encontrar aquí . El problema original se debía a un error menor (ver esta discusión en github ) y mi respuesta en realidad no apuntaba correctamente (disculpas por la confusión causada). Como todo eso ya no importa (el error está corregido), he editado mi respuesta para centrarme en cómo se puede resolver el LDA mediante SVD, que es el algoritmo por defecto en scikit-learn .


Tras definir las matrices de dispersión dentro y entre clases $\boldsymbol \Sigma_W$ y $\boldsymbol \Sigma_B$ El cálculo estándar de LDA, como se indica en su pregunta, consiste en tomar los vectores propios de $\boldsymbol \Sigma_W^{-1} \boldsymbol \Sigma_B$ como ejes discriminantes ( véase, por ejemplo, aquí ). Sin embargo, los mismos ejes pueden calcularse de una manera ligeramente diferente, explotando una matriz de blanqueo:

  1. Computar $\boldsymbol \Sigma_W^{-1/2}$ . Esto es una transformación blanqueadora con respecto a la covarianza agrupada dentro de la clase (véase mi respuesta vinculada para más detalles).

    Tenga en cuenta que si tiene una descomposición propia $\boldsymbol \Sigma_W = \mathbf{U}\mathbf{S}\mathbf{U}^\top$ entonces $\boldsymbol \Sigma_W^{-1/2}=\mathbf{U}\mathbf{S}^{-1/2}\mathbf{U}^\top$ . Obsérvese también que se puede calcular lo mismo haciendo la SVD de los datos agrupados dentro de la clase: $\mathbf{X}_W = \mathbf{U} \mathbf{L} \mathbf{V}^\top \Rightarrow \boldsymbol\Sigma_W^{-1/2}=\mathbf{U}\mathbf{L}^{-1}\mathbf{U}^\top$ .

  2. Encontrar los vectores propios de $\boldsymbol \Sigma_W^{-1/2} \boldsymbol \Sigma_B \boldsymbol \Sigma_W^{-1/2}$ Llamémoslos $\mathbf{A}^*$ .

    Una vez más, hay que tener en cuenta que se puede calcular haciendo la SVD de los datos entre clases $\mathbf{X}_B$ transformado con $\boldsymbol \Sigma_W^{-1/2}$ es decir, datos entre clases blanqueados con respecto a la covarianza dentro de la clase.

  3. Los ejes discriminantes $\mathbf A$ estará dada por $\boldsymbol \Sigma_W^{-1/2} \mathbf{A}^*$ es decir, por los ejes principales de los datos transformados, transformado de nuevo .

    De hecho, si $\mathbf a^*$ es un vector propio de la matriz anterior, entonces $$\boldsymbol \Sigma_W^{-1/2} \boldsymbol \Sigma_B \boldsymbol \Sigma_W^{-1/2}\mathbf a^* = \lambda \mathbf a^*,$$ y multiplicando desde la izquierda por $\boldsymbol \Sigma_W^{-1/2}$ y definiendo $\mathbf a = \boldsymbol \Sigma_W^{-1/2}\mathbf a^*$ obtenemos inmediatamente: $$\boldsymbol \Sigma_W^{-1} \boldsymbol \Sigma_B \mathbf a = \lambda \mathbf a.$$

En resumen, el LDA equivale a blanquear la matriz de las medias de las clases con respecto a la covarianza dentro de la clase, realizar un ACP sobre las medias de las clases y volver a transformar los ejes principales resultantes en el espacio original (sin blanquear).

Esto se señala, por ejemplo, en Los elementos del aprendizaje estadístico En la sección 4.3.3. En scikit-learn esta es la forma predeterminada de calcular el LDA porque la SVD de una matriz de datos es numéricamente más estable que la descomposición propia de su matriz de covarianza.

Tenga en cuenta que se puede utilizar cualquier transformación blanqueadora en lugar de $\boldsymbol \Sigma_W^{-1/2}$ y todo seguirá funcionando exactamente igual. En scikit-learn $\mathbf{L}^{-1}\mathbf{U}^\top$ (en lugar de $\mathbf{U}\mathbf{L}^{-1}\mathbf{U}^\top$ ), y funciona bien (al contrario de lo que estaba escrito originalmente en mi respuesta).

1 votos

Gracias por esta bonita respuesta. Te agradezco que te hayas tomado el tiempo de redactarla tan bien. Tal vez usted podría mencionarlo en la discusión en GitHub; Estoy seguro de que sería de ayuda para arreglar el LDA en la próxima versión de sci-kit

0 votos

@SebastianRaschka: No tengo una cuenta en GitHub. Pero si quieres, puedes dar allí un enlace a este hilo.

0 votos

@amoeba: Los libros de texto suelen describir el LDA como tú lo has hecho: una descomposición de valores propios de $\boldsymbol \Sigma_W^{-1} \boldsymbol \Sigma_B$ . Curiosamente, varias implementaciones de LDA que conozco adoptan un enfoque diferente. Sus ejes son los vectores de las medias de las clases transformados con $\boldsymbol \Sigma_W^{-1}$ . Su solución LDA es una base ortonormal de estos vectores. El LDA de Scikit-learn da los mismos resultados que estas implementaciones, así que no creo que haya realmente un error.

3voto

David Puntos 31

Solo para cerrar esta pregunta, el problema discutido con el LDA ha sido arreglado en scikit-learn 0.15.2 .

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X