Pourquoi le LDA scikit-learn de Python ne fonctionne-t-il pas correctement et comment calcule-t-il le LDA via SVD?

26

J'utilisais l'analyse linéaire discriminante (LDA) de la scikit-learnbibliothèque d'apprentissage automatique (Python) pour réduire la dimensionnalité et j'étais un peu curieux des résultats. Je me demande maintenant ce que fait la LDA scikit-learnpour que les résultats soient différents, par exemple, d'une approche manuelle ou d'une LDA effectuée en R. Ce serait formidable si quelqu'un pouvait me donner quelques informations ici.

Ce qui est fondamentalement le plus préoccupant, c'est que le scikit-plotmontre une corrélation entre les deux variables où il devrait y avoir une corrélation 0.

Pour un test, j'ai utilisé l'ensemble de données Iris et les 2 premiers discriminants linéaires ressemblaient à ceci:

IMG-1. LDA via scikit-learn

entrez la description de l'image ici

Ceci est fondamentalement cohérent avec les résultats que j'ai trouvés dans la documentation scikit-learn ici.

Maintenant, j'ai parcouru la LDA étape par étape et j'ai obtenu une projection différente. J'ai essayé différentes approches afin de savoir ce qui se passait:

IMG-2. LDA sur données brutes (pas de centrage, pas de standardisation)

entrez la description de l'image ici

Et voici l'approche étape par étape si je standardisais (normalisation du score z; variance unitaire) les données en premier. J'ai fait la même chose avec le centrage moyen uniquement, ce qui devrait conduire à la même image de projection relative (et c'est effectivement le cas).

IMG-3. LDA étape par étape après centrage moyen ou standardisation

entrez la description de l'image ici

IMG-4. LDA dans R (paramètres par défaut)

LDA dans IMG-3 où j'ai centré les données (ce qui serait l'approche préférée) ressemble également exactement à celle que j'ai trouvée dans un message par quelqu'un qui a fait le LDA dans R entrez la description de l'image ici


Code de référence

Je ne voulais pas coller tout le code ici, mais je l'ai téléchargé en tant que bloc-notes IPython ici divisé en plusieurs étapes que j'ai utilisées (voir ci-dessous) pour la projection LDA.

  1. Étape 1: calcul des vecteurs moyens à dimensions d
    mi=1nixDinxk
  2. Étape 2: Calcul des matrices de dispersion

    2.1 La matrice de dispersion intra-classe est calculée par l'équation suivante:SW

    SW=i=1cSi=i=1cxDin(xmi)(xmi)T

    2.2 La matrice de dispersion entre classes est calculée par l'équation suivante: où est la moyenne globale.SB

    SB=i=1cni(mim)(mim)T
    m
  3. Étape 3. Résolution du problème des valeurs propres généralisées pour la matriceSW1SB

    3.1. Tri des vecteurs propres en diminuant les valeurs propres

    3.2. Choisir k vecteurs propres avec les plus grandes valeurs propres. Combiner les deux vecteurs propres avec les valeurs propres les plus élevées pour construire notre matrice de vecteurs propresd×kW

  4. Étape 5: Transformer les échantillons dans le nouveau sous-espace

    y=WT×x.
amibe dit réintégrer Monica
la source
Je n'ai pas cherché les différences, mais vous pouvez voir exactement ce que fait scikit-learn dans la source .
Dougal
Il semble qu'ils normalisent également (centrage puis mise à l'échelle via la division par l'écart-type). Cela, je m'attendrais à un résultat similaire à celui de mon 3ème tracé (et le tracé R) ... hmm
Bizarre: l'intrigue que vous avez obtenue avec scikit (et celle qu'ils montrent dans leur documentation) n'a pas de sens. LDA donne toujours des projections qui ont une corrélation nulle, mais il existe évidemment une très forte corrélation entre les projections de scikit sur les axes discriminants 1 et 2. Quelque chose cloche clairement là-bas.
Amoeba dit Reinstate Monica
@ameoba Oui, je le pense aussi. Ce qui est également bizarre, c'est que le même tracé que je montre pour scikit se trouve dans la documentation d'exemple: scikit-learn.org/stable/auto_examples/decomposition/… Cela me fait penser que mon utilisation de scikit est correcte, mais qu'il y a quelque chose d'étrange à propos de la fonction LDA
@SebastianRaschka: Oui, j'ai remarqué. C'est vraiment bizarre. Cependant, notez que le premier de vos propres graphiques LDA (non scikit) montre également une corrélation non nulle et que quelque chose doit donc également être erroné. Avez-vous centré les données? La projection sur le deuxième axe ne semble pas avoir une moyenne nulle.
Amoeba dit Reinstate Monica

Réponses:

20

Mise à jour: Grâce à cette discussion, a scikit-learnété mis à jour et fonctionne correctement maintenant. Son code source LDA peut être trouvé ici . Le problème d'origine était dû à un bug mineur (voir cette discussion github ) et ma réponse ne le pointait pas correctement (excuses pour toute confusion causée). Comme tout cela n'a plus d'importance (bug corrigé), j'ai modifié ma réponse pour me concentrer sur la façon dont LDA peut être résolu via SVD, qui est l'algorithme par défaut dans scikit-learn.


Après avoir défini les matrices de dispersion et -classes et , le calcul LDA standard, comme indiqué dans votre question, consiste à prendre des vecteurs propres de comme axes discriminants ( voir par exemple ici ). Cependant, les mêmes axes peuvent être calculés de manière légèrement différente, en exploitant une matrice de blanchiment:ΣWΣBΣW1ΣB

  1. Calculez . Il s'agit d' une transformation blanchissante par rapport à la covariance regroupée au sein de la classe (voir ma réponse liée pour plus de détails).ΣW1/2

    Notez que si vous avez une décomposition propre , alors . Notez également que l'on calcule la même chose en faisant SVD des données regroupées au sein de la classe: .ΣW=USUΣW1/2=US1/2UXW=ULVΣW1/2=UL1U

  2. Trouvez les vecteurs propres de , appelons-les .ΣW1/2ΣBΣW1/2A

    Encore une fois, notez que l'on peut le calculer en faisant SVD des données entre classes , transformées avec , c'est-à-dire des données entre classes blanchies par rapport à l'intérieur de la classe covariance.XBΣW1/2

  3. Les axes discriminants seront donnés par , c'est-à-dire par les axes principaux des données transformées , transformées à nouveau .AΣW1/2A

    En effet, si est un vecteur propre de la matrice ci-dessus, alors et en multipliant à partir de la gauche par et en définissant , nous obtenons immédiatement :a

    ΣW1/2ΣBΣW1/2a=λa,
    ΣW1/2a=ΣW1/2a
    ΣW1ΣBa=λa.

En résumé, LDA équivaut à blanchir la matrice des moyennes de classe par rapport à la covariance intra-classe, à effectuer l'ACP sur les moyennes de classe et à retransformer les axes principaux résultants en l'espace d'origine (non blanchi).

Ceci est souligné par exemple dans Les éléments de l'apprentissage statistique , section 4.3.3. C'est scikit-learnla façon par défaut de calculer LDA car la SVD d'une matrice de données est numériquement plus stable que la décomposition propre de sa matrice de covariance.

Notez que l'on peut utiliser n'importe quelle transformation de blanchiment au lieu de et tout fonctionnera toujours exactement de la même manière. Dans est utilisé (au lieu de ), et cela fonctionne très bien (contrairement à ce qui était écrit à l'origine dans ma réponse).ΣW1/2scikit-learn L1UUL1U

amibe dit réintégrer Monica
la source
1
Merci pour cette belle réponse. J'apprécie que vous ayez pris le temps de bien l'écrire. Vous pourriez peut-être le mentionner dans la discussion sur GitHub; Je suis sûr que ce serait utile pour corriger le LDA dans la prochaine version de sci-kit
@SebastianRaschka: Je n'ai pas de compte sur GitHub. Mais si vous le souhaitez, vous pouvez y donner un lien vers ce fil.
amibe dit Réintégrer Monica
@amoeba: Les manuels décrivent généralement la LDA comme vous l'avez fait - une décomposition de valeurs propres de . Curieusement, un certain nombre d'implémentations LDA que je connais adoptent une approche différente. Leurs axes sont les vecteurs de la classe moyenne transformée avec . Votre solution LDA est une base orthonormée de ces vecteurs. Le LDA de Scikit-learn donne les mêmes résultats que ces implémentations, donc je ne pense pas qu'il y ait réellement une erreur. ΣW1ΣBΣW1
kazemakase
2
@kazemakase: Eh bien, bien sûr, s'il n'y a que deux classes, alors a le rang 1, et tout simplifie beaucoup, car le seul vecteur propre de est donné par , où sont des moyennes de classe. Je suppose que c'est ce que tu voulais dire avant? Ceci est bien couvert par exemple dans le manuel de Bishop's ML, section 4.1.4. Mais la généralisation à plus de classes nécessite une analyse propre (Ibid., 4.1.6). En outre, le code de scikit (que nous discutons ici!) Fait usage SVD, deux fois en fait. ΣBΣW1ΣBΣW1(μ1μ2)μi
amibe dit Réintégrer Monica
3

Juste pour fermer cette question, le problème discuté avec le LDA a été corrigé dans scikit-learn 0.15.2 .


la source