Por que o LDA scikit-learn do Python não está funcionando corretamente e como ele calcula o LDA via SVD?

26

Eu estava usando a Análise Discriminante Linear (LDA) da scikit-learnbiblioteca de aprendizado de máquina (Python) para redução de dimensionalidade e fiquei um pouco curioso sobre os resultados. Gostaria de saber agora o que o LDA scikit-learnestá fazendo para que os resultados pareçam diferentes, por exemplo, de uma abordagem manual ou de um LDA feito em R. Seria ótimo se alguém pudesse me dar algumas idéias aqui.

O que é mais preocupante é que scikit-plotmostra uma correlação entre as duas variáveis ​​em que deve haver uma correlação 0.

Para um teste, usei o conjunto de dados Iris e os dois primeiros discriminantes lineares ficaram assim:

IMG-1. LDA via scikit-learn

insira a descrição da imagem aqui

Isso é basicamente consistente com os resultados que encontrei na documentação do scikit-learn aqui.

Agora, passei pelo LDA passo a passo e recebi uma projeção diferente. Tentei abordagens diferentes para descobrir o que estava acontecendo:

IMG-2. LDA em dados brutos (sem centralização, sem padronização)

insira a descrição da imagem aqui

E aqui estaria a abordagem passo a passo se eu padronizasse (normalização do escore z; variação da unidade) primeiro os dados. Fiz a mesma coisa apenas com a centralização da média, o que deve levar à mesma imagem de projeção relativa (e com efeito).

IMG-3 LDA passo a passo após centralização média ou padronização

insira a descrição da imagem aqui

IMG-4. LDA em R (configurações padrão)

O LDA no IMG-3, onde eu centralizei os dados (qual seria a abordagem preferida) também parece exatamente o mesmo que encontrei em um Post por alguém que fez o LDA no R insira a descrição da imagem aqui


Código de referência

Eu não queria colar todo o código aqui, mas o enviei como um bloco de notas IPython aqui, dividido nas várias etapas que usei (veja abaixo) para a projeção do LDA.

  1. Etapa 1: Computando os vetores médios d-dimensionais
    mi=1nixDinxk
  2. Etapa 2: Computando as matrizes de dispersão

    2.1 A matriz de dispersão dentro da classe é calculada pela seguinte equação:S W = c Σ i = 1 S i = c ΣSW

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

    2.2 A matriz de dispersão entre classes é calculada pela seguinte equação: onde é a média geral.S B = c i = 1 n i ( m i - m ) ( m i - m ) T mSB

    SB=i=1cni(mim)(mim)T
    m
  3. Etapa 3. Resolvendo o problema generalizado de autovalor para a matrizSW1SB

    3.1 Classificando os vetores próprios, diminuindo os valores próprios

    3.2 Escolhendo k vetores próprios com os maiores valores próprios. Combinando os dois vetores próprios com os valores próprios mais altos para construir nossa matriz de vetores próprios dimensionalWd×kW

  4. Etapa 5: Transformando as amostras no novo subespaço

    y=WT×x.
ameba diz Restabelecer Monica
fonte
Ainda não procurei as diferenças, mas você pode ver exatamente o que o scikit-learn está fazendo na fonte .
Dougal 28/07
Parece que eles também estão padronizando (centralizando e depois escalando via divisão pelo desvio padrão). Isso, eu esperaria um resultado semelhante ao de minha 3ª parcela (e R) enredo ... hmm
Estranho: o gráfico que você obteve com o scikit (e o que eles mostram na documentação) não faz sentido. O LDA sempre produz projeções com correlação zero, mas obviamente existe uma correlação muito forte entre as projeções do scikit nos eixos discriminantes 1 e 2. Algo está claramente errado lá.
Ameba diz Reinstate Monica
@ameoba Sim, eu também acho. O que também é estranho é que o mesmo gráfico que estou mostrando para o scikit está na documentação de exemplo: scikit-learn.org/stable/auto_examples/decomposition/… Isso me faz pensar que meu uso do scikit está correto, mas que há algo estranho sobre a função LDA
@SebastianRaschka: Sim, eu notei. É realmente estranho. No entanto, observe que o primeiro gráfico de LDA (sem scikit) também mostra correlação diferente de zero e, portanto, algo também deve estar errado com ele. Você centralizou os dados? A projeção no segundo eixo não parece ter média zero.
Ameba diz Reinstate Monica

Respostas:

20

Atualização: graças a esta discussão, scikit-learnfoi atualizada e funciona corretamente agora. Seu código fonte LDA pode ser encontrado aqui . O problema original ocorreu devido a um bug menor (consulte esta discussão no github ) e minha resposta não estava apontando corretamente (desculpas por qualquer confusão causada). Como tudo isso não importa mais (o bug foi corrigido), editei minha resposta para focar em como o LDA pode ser resolvido via SVD, que é o algoritmo padrão scikit-learn.


Depois de definir matrizes de dispersão dentro e entre classes e , o cálculo padrão do LDA, como apontado em sua pergunta, é obter vetores próprios de como eixos discriminantes ( veja, por exemplo, aqui ). Os mesmos eixos, no entanto, podem ser calculados de uma maneira ligeiramente diferente, explorando uma matriz de branqueamento:ΣWΣBΣW1ΣB

  1. Calcule . Esta é uma transformação de clareamento em relação à covariância dentro da classe (veja minha resposta vinculada para obter detalhes).ΣW1/2

    Observe que se você tiver decomposição própria , . Observe também que é possível calcular o mesmo fazendo o SVD dos dados agrupados dentro da classe: .ΣW=USUΣW1/2=US1/2UXW=ULVΣW1/2=UL1U

  2. Encontre vetores próprios de , vamos chamá-los de .ΣW1/2ΣBΣW1/2A

    Novamente, observe que é possível computá-lo fazendo SVD de dados entre classes , transformados com , ou seja, dados entre classes embranquecidos em relação à classe dentro covariância.XBΣW1/2

  3. Os eixos discriminantes serão dados por , isto é, pelos eixos principais dos dados transformados , transformados novamente .AΣW1/2A

    De fato, se é um vetor próprio da matriz acima, então e multiplicando da esquerda por e definindo , obtemos imediatamente :Σ - 1 / 2 W Σa

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

Em resumo, o LDA equivale a embranquecer a matriz de médias de classe em relação à covariância dentro da classe, executando o PCA nos meios de classe e transformando os eixos principais resultantes no espaço original (não branqueado).

Isso é apontado, por exemplo, em Os elementos do aprendizado estatístico , seção 4.3.3. Em scikit-learnesta é a maneira padrão para calcular LDA porque SVD de uma matriz de dados é numericamente mais estável do que eigen-decomposição da sua matriz de covariância.

Observe que é possível usar qualquer transformação de clareamento em vez de e tudo continuará funcionando exatamente da mesma maneira. Em é usado (em vez de ) e funciona muito bem (ao contrário do que foi originalmente escrito na minha resposta). L - 1 LL L - 1 LΣW1/2scikit-learn L1UUL1U

ameba diz Restabelecer Monica
fonte
11
Obrigado por esta boa resposta. Agradeço que você tenha dedicado um tempo para escrevê-lo dessa maneira. Talvez você possa mencionar isso na discussão no GitHub; Estou certo de que seria de ajuda para corrigir o LDA na próxima versão do sci-kit
@SebastianRaschka: Eu não tenho uma conta no GitHub. Mas se você quiser, pode fornecer um link para este tópico.
Ameba diz Reinstate Monica
@amoeba: Os livros didáticos geralmente descrevem o LDA como você fez - uma decomposição de autovalor de . Curiosamente, várias implementações de LDA que conheço adotam uma abordagem diferente. Seus eixos são os vetores para as médias da classe transformadas com . Sua solução LDA é uma base ortonormal desses vetores. O LDA do Scikit-learn fornece os mesmos resultados que essas implementações, então não acho que exista realmente um erro. Σ - 1 WΣW1ΣBΣW1
Kazemakase
2
@kazemakase: Bem, é claro que se houver apenas duas classes, então tem classificação 1 e tudo simplifica muito, já que o único autovetor de é dado por , onde são médias de classe. Eu acho que é isso que você quis dizer antes? Isso é bem abordado, por exemplo, no livro de ML de Bishop, seção 4.1.4. Mas a generalização para mais classes requer análise própria (Ibid., 4.1.6). Além disso, o código do scikit (que estamos discutindo aqui!) Faz uso svd, duas vezes, na verdade. Σ - 1 W Σ B Σ - 1 W ( μ 1 - μ 2 ) μ iΣBΣW1ΣBΣW1(μ1μ2)μi
Ameba diz Reinstate Monica
3

Apenas para encerrar esta questão, o problema discutido com o LDA foi corrigido no scikit-learn 0.15.2 .


fonte