Continuando nossa exploração da Análise Discriminante Linear, chegamos a uma aplicação particularmente valiosa: a redução de dimensionalidade. Enquanto anteriormente discutimos o LDA como classificador, agora focaremos em sua capacidade de projetar dados em espaços de menor dimensão preservando a separabilidade entre classes.
O Conceito de Redução de Dimensionalidade com LDA
Conforme observamos na seção anterior, o LDA busca encontrar eixos que maximizem a separação entre classes. Analogamente, quando aplicado para redução de dimensionalidade, o LDA identifica as direções que melhor discriminam as classes, projetando os dados em um espaço com dimensionalidade reduzida.
Fundamentos Matemáticos
O LDA para redução de dimensionalidade baseia-se na maximização do critério de Fisher:
\(J(w) = \frac{w^T S_B w}{w^T S_W w}\)Onde:
- S_B é a matriz de dispersão entre classes (between-class scatter)
- S_W é a matriz de dispersão dentro das classes (within-class scatter)
- w são os vetores de projeção que maximizam a separação
Inegavelmente, esta abordagem encontra automaticamente as direções que preservam a informação mais discriminativa dos dados.
Diferenças entre LDA e PCA
Embora tanto LDA quanto PCA realizem redução de dimensionalidade, seus objetivos fundamentais diferem significativamente:
- PCA: Maximiza a variância total dos dados (não supervisionado)
- LDA: Maximiza a separação entre classes (supervisionado)
Portanto, o LDA é especialmente útil quando temos labels disponíveis e queremos preservar a estrutura de classes durante a redução de dimensionalidade.
Implementação no scikit-learn
No scikit-learn, a redução de dimensionalidade com LDA é realizada através da mesma classe LinearDiscriminantAnalysis, mas especificando o número de componentes desejado:
- O parâmetro
n_componentscontrola a dimensionalidade do espaço projetado - O número máximo de componentes é
min(n_classes - 1, n_features) - O método
transformrealiza a projeção dos dados
Vantagens da Abordagem
- Preserva a informação discriminativa entre classes
- Reduz ruído e redundância nas features
- Melhora a performance de classificadores subsequentes
- Facilita visualização de dados multivariados
Aplicações Práticas
Primordialmente, a redução de dimensionalidade com LDA é útil em cenários como:
- Pré-processamento para outros algoritmos de machine learning
- Visualização de dados de alta dimensionalidade
- Extração de features para sistemas de reconhecimento
- Análise exploratória de dados com labels
Exemplo Prático em Python
Para ilustrar a redução de dimensionalidade com LDA, vejamos um exemplo completo com visualização:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
import numpy as np import matplotlib.pyplot as plt from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.datasets import load_iris, make_classification from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import accuracy_score ''' Demonstração da redução de dimensionalidade usando LDA com comparação entre dados originais e projetados ''' # Carregando dataset Iris como exemplo clássico iris = load_iris() X, y = iris.data, iris.target feature_names = iris.feature_names target_names = iris.target_names print("=== INFORMAÇÕES DO DATASET ORIGINAL ===") print(f"Dimensionalidade original: {X.shape}") print(f"Número de classes: {len(np.unique(y))}") print(f"Nomes das classes: {list(target_names)}") print(f"Features originais: {list(feature_names)}") ''' Aplicando LDA para redução de dimensionalidade ''' # Primeiramente, padronizamos os dados scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Aplicando LDA com redução para 2 componentes (máximo possível para 3 classes) lda = LinearDiscriminantAnalysis(n_components=2) X_lda = lda.fit_transform(X_scaled, y) print(f"\n=== REDUÇÃO DE DIMENSIONALIDADE ===") print(f"Dimensionalidade após LDA: {X_lda.shape}") print(f"Variância explicada por componente: {lda.explained_variance_ratio_}") print(f"Variância total explicada: {sum(lda.explained_variance_ratio_):.3f}") ''' Visualização comparativa: dados originais vs dados projetados ''' fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 5)) # Plot 1: Duas features originais para comparação for i, target_name in enumerate(target_names): ax1.scatter(X_scaled[y == i, 0], X_scaled[y == i, 1], alpha=0.8, label=target_name) ax1.set_title('Duas Features Originais (Padronizadas)') ax1.set_xlabel('Comprimento da Sépala (padronizado)') ax1.set_ylabel('Largura da Sépala (padronizado)') ax1.legend() ax1.grid(True, alpha=0.3) # Plot 2: Projeção LDA for i, target_name in enumerate(target_names): ax2.scatter(X_lda[y == i, 0], X_lda[y == i, 1], alpha=0.8, label=target_name) ax2.set_title('Projeção LDA (2 Componentes)') ax2.set_xlabel('Componente LDA 1') ax2.set_ylabel('Componente LDA 2') ax2.legend() ax2.grid(True, alpha=0.3) # Plot 3: Apenas primeira componente LDA (1D) for i, target_name in enumerate(target_names): ax3.scatter(X_lda[y == i, 0], np.zeros_like(X_lda[y == i, 0]), alpha=0.8, label=target_name) ax3.set_title('Projeção LDA (1 Componente)') ax3.set_xlabel('Componente LDA 1') ax3.set_yticks([]) ax3.legend() ax3.grid(True, alpha=0.3) plt.tight_layout() plt.show() ''' Avaliando o impacto na classificação ''' print("\n=== IMPACTO NA CLASSIFICAÇÃO ===") # Dividindo os dados X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.3, random_state=42 ) # Classificador nos dados originais lda_original = LinearDiscriminantAnalysis() lda_original.fit(X_train, y_train) y_pred_original = lda_original.predict(X_test) accuracy_original = accuracy_score(y_test, y_pred_original) # Classificador nos dados reduzidos (2 componentes) X_train_lda = lda.transform(X_train) X_test_lda = lda.transform(X_test) lda_reduced = LinearDiscriminantAnalysis() lda_reduced.fit(X_train_lda, y_train) y_pred_reduced = lda_reduced.predict(X_test_lda) accuracy_reduced = accuracy_score(y_test, y_pred_reduced) print(f"Acurácia com dados originais (4D): {accuracy_original:.3f}") print(f"Acurácia com dados reduzidos (2D): {accuracy_reduced:.3f}") ''' Exemplo com dataset de maior dimensionalidade ''' print("\n=== EXEMPLO COM ALTA DIMENSIONALIDADE ===") # Criando dataset artificial com mais features X_high, y_high = make_classification( n_samples=200, n_features=20, n_informative=5, n_redundant=10, n_classes=3, random_state=42 ) X_high_scaled = StandardScaler().fit_transform(X_high) # LDA para redução lda_high = LinearDiscriminantAnalysis(n_components=2) X_high_lda = lda_high.fit_transform(X_high_scaled, y_high) print(f"Dimensionalidade original: {X_high.shape}") print(f"Dimensionalidade após LDA: {X_high_lda.shape}") # Visualização da redução de 20D para 2D plt.figure(figsize=(10, 6)) for i in range(3): plt.scatter(X_high_lda[y_high == i, 0], X_high_lda[y_high == i, 1], alpha=0.7, label=f'Classe {i}') plt.title('Redução LDA: 20D → 2D') plt.xlabel('Componente LDA 1') plt.ylabel('Componente LDA 2') plt.legend() plt.grid(True, alpha=0.3) plt.show() ''' Análise dos componentes LDA ''' print("\n=== ANÁLISE DOS COMPONENTES LDA ===") print("Coeficientes dos componentes LDA (pesos das features originais):") if hasattr(lda, 'scalings_'): for comp_idx in range(lda.scalings_.shape[1]): print(f"Componente {comp_idx + 1}:") for feat_idx, feat_name in enumerate(feature_names): weight = lda.scalings_[feat_idx, comp_idx] print(f" {feat_name}: {weight:.3f}") ''' Interpretação prática dos resultados ''' print("\n=== INTERPRETAÇÃO PRÁTICA ===") print("O LDA para redução de dimensionalidade oferece:") print("1. Preservação da estrutura de classes em espaço reduzido") print("2. Componentes que maximizam a separação entre grupos") print("3. Visualização eficiente de dados multivariados") print("4. Pré-processamento para algoritmos subsequentes") |
Interpretação dos Resultados
Analisando o exemplo, podemos observar que:
- O LDA consegue reduzir significativamente a dimensionalidade mantendo a separabilidade
- Os componentes LDA capturam direções que maximizam a distância entre classes
- Mesmo com redução drástica (de 20D para 2D), a estrutura discriminativa é preservada
- A performance de classificação pode ser mantida ou mesmo melhorada com a redução
Considerações Importantes
Limitações do LDA
Embora poderoso, o LDA para redução de dimensionalidade tem algumas limitações:
- Assume distribuição normal dos dados
- Presume matrizes de covariância iguais entre classes
- Número máximo de componentes é limitado pelo número de classes
- Sensível a outliers nos dados
Melhores Práticas
Para obter os melhores resultados com LDA:
- Sempre padronize os dados antes de aplicar LDA
- Verifique os pressupostos quando possível
- Use validação cruzada para determinar o número ideal de componentes
- Combine com outras técnicas de pré-processamento quando necessário
Aplicações em Cenários Reais
Similarmente às aplicações que discutimos anteriormente, a redução de dimensionalidade com LDA é particularmente útil em:
- Reconhecimento facial e biométrica
- Análise de expressão gênica
- Processamento de sinais médicos
- Análise de documentos e texto
Portanto, o LDA representa uma ferramenta valiosa no arsenal de técnicas de redução de dimensionalidade, especialmente quando trabalhamos com dados supervisionados e buscamos preservar a informação discriminativa entre classes. Inclusive, sua combinação com outros métodos pode levar a soluções ainda mais robustas e eficientes.