import numpy as np
import matplotlib.pyplot as plt
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import make_classification, load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
import time
import warnings
warnings.filterwarnings('ignore')
'''
Estudo comparativo dos diferentes algoritmos de estimação (solvers)
disponíveis no LDA do scikit-learn
'''
print("=== COMPARAÇÃO DE ALGORITMOS DE ESTIMAÇÃO EM LDA ===")
# Criando diferentes cenários de dados
print("\n1. CONFIGURAÇÃO DOS CENÁRIOS DE TESTE")
# Cenário 1: Dataset balanceado com dimensionalidade moderada
X1, y1 = make_classification(
n_samples=1000, n_features=20, n_informative=10,
n_redundant=5, n_classes=3, random_state=42
)
# Cenário 2: Alta dimensionalidade com poucas amostras
X2, y2 = make_classification(
n_samples=100, n_features=50, n_informative=10,
n_redundant=30, n_classes=2, random_state=42
)
# Cenário 3: Dataset real (Iris) para referência
iris = load_iris()
X3, y3 = iris.data, iris.target
cenarios = [
('Moderado (1000x20)', X1, y1),
('Alta Dim (100x50)', X2, y2),
('Real (Iris)', X3, y3)
]
# Solvers a serem testados
solvers = ['svd', 'lsqr', 'eigen']
shrinkage_values = [None, 'auto', 0.5]
'''
Avaliação sistemática dos solvers em diferentes cenários
'''
print("\n2. AVALIAÇÃO DOS SOLVERS")
resultados_completos = {}
for cenario_nome, X, y in cenarios:
print(f"\n--- {cenario_nome} ---")
print(f"Dimensões: {X.shape}, Classes: {len(np.unique(y))}")
# Padronizando os dados
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Dividindo em treino e teste
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.3, random_state=42
)
cenario_resultados = {}
for solver in solvers:
for shrinkage in shrinkage_values:
# Configuração específica para cada combinação
if solver == 'svd' and shrinkage is not None:
continue # SVD não suporta shrinkage
try:
# Medindo tempo de treinamento
start_time = time.time()
if shrinkage is not None:
lda = LinearDiscriminantAnalysis(solver=solver, shrinkage=shrinkage)
else:
lda = LinearDiscriminantAnalysis(solver=solver)
# Treinamento
lda.fit(X_train, y_train)
train_time = time.time() - start_time
# Previsões e acurácia
y_pred = lda.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
# Validação cruzada para avaliação robusta
cv_scores = cross_val_score(lda, X_scaled, y, cv=5)
cv_mean = np.mean(cv_scores)
cv_std = np.std(cv_scores)
# Armazenando resultados
config_key = f"{solver}_shrink{shrinkage}"
cenario_resultados[config_key] = {
'solver': solver,
'shrinkage': shrinkage,
'accuracy': accuracy,
'cv_mean': cv_mean,
'cv_std': cv_std,
'train_time': train_time,
'model': lda
}
print(f" {config_key}:")
print(f" Acurácia: {accuracy:.3f}, CV: {cv_mean:.3f} ± {cv_std:.3f}")
print(f" Tempo: {train_time:.4f}s")
except Exception as e:
print(f" {config_key}: ERRO - {e}")
resultados_completos[cenario_nome] = cenario_resultados
'''
Análise de performance computacional
'''
print("\n3. ANÁLISE DE PERFORMANCE COMPUTACIONAL")
# Coletando dados de tempo para comparação
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
# Gráfico 1: Tempo de treinamento por cenário
for idx, (cenario_nome, resultados) in enumerate(resultados_completos.items()):
ax = axes[0, 0] if idx == 0 else axes[0, 1] if idx == 1 else axes[1, 0]
configs = []
tempos = []
for config_key, dados in resultados.items():
configs.append(config_key)
tempos.append(dados['train_time'])
bars = ax.bar(configs, tempos, color=['blue', 'orange', 'green'][:len(configs)])
ax.set_title(f'Tempo de Treinamento - {cenario_nome}')
ax.set_ylabel('Tempo (segundos)')
ax.tick_params(axis='x', rotation=45)
# Adicionando valores nas barras
for bar, tempo in zip(bars, tempos):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.001,
f'{tempo:.4f}s', ha='center', va='bottom', fontsize=8)
# Gráfico 2: Comparação de acurácia entre solvers
ax_acc = axes[1, 1]
solver_accuracies = {solver: [] for solver in solvers}
for cenario_nome, resultados in resultados_completos.items():
for solver in solvers:
# Buscando a melhor configuração para cada solver
solver_configs = [k for k in resultados.keys() if k.startswith(solver)]
if solver_configs:
best_config = max(solver_configs, key=lambda k: resultados[k]['cv_mean'])
solver_accuracies[solver].append(resultados[best_config]['cv_mean'])
# Plotando acurácias médias
cenario_nomes = [nome for nome, _ in cenarios]
x_pos = np.arange(len(cenario_nomes))
width = 0.25
for idx, solver in enumerate(solvers):
if solver_accuracies[solver]: # Verifica se há dados
ax_acc.bar(x_pos + idx*width, solver_accuracies[solver], width, label=solver)
ax_acc.set_xlabel('Cenários')
ax_acc.set_ylabel('Acurácia (CV)')
ax_acc.set_title('Acurácia por Solver e Cenário')
ax_acc.set_xticks(x_pos + width)
ax_acc.set_xticklabels(cenario_nomes)
ax_acc.legend()
ax_acc.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
'''
Análise de estabilidade numérica
'''
print("\n4. ANÁLISE DE ESTABILIDADE NUMÉRICA")
# Testando casos extremos
print("\nCenários Extremos:")
# Caso 1: Mais features que amostras
X_extremo1, y_extremo1 = make_classification(
n_samples=50, n_features=100, n_informative=10,
n_classes=2, random_state=42
)
# Caso 2: Matriz quase singular
X_extremo2 = np.random.randn(200, 10)
# Criando correlações fortes para tornar a matriz quase singular
X_extremo2[:, 5] = X_extremo2[:, 4] + 0.01 * np.random.randn(200)
X_extremo2[:, 6] = X_extremo2[:, 4] + 0.01 * np.random.randn(200)
y_extremo2 = np.random.randint(0, 2, 200)
cenarios_extremos = [
('Mais Features (50x100)', X_extremo1, y_extremo1),
('Quase Singular (200x10)', X_extremo2, y_extremo2)
]
for cenario_nome, X, y in cenarios_extremos:
print(f"\n{cenario_nome}:")
X_scaled = StandardScaler().fit_transform(X)
for solver in solvers:
try:
if solver == 'svd':
lda = LinearDiscriminantAnalysis(solver=solver)
else:
lda = LinearDiscriminantAnalysis(solver=solver, shrinkage='auto')
lda.fit(X_scaled, y)
score = lda.score(X_scaled, y)
print(f" {solver}: OK (score: {score:.3f})")
except Exception as e:
print(f" {solver}: FALHOU - {e}")
'''
Recomendações práticas baseadas nos resultados
'''
print("\n5. RECOMENDAÇÕES PRÁTICAS")
print("\nBaseado na análise experimental, recomenda-se:")
print("\nPara datasets gerais:")
print(" - Usar 'svd' como primeira opção (mais estável)")
print(" - Especificar solver apenas se necessário")
print(" - Deixar a seleção automática quando possível")
print("\nQuando usar shrinkage:")
print(" - Preferir 'lsqr' ou 'eigen'")
print(" - 'lsqr' geralmente mais robusto para alta dimensionalidade")
print(" - 'eigen' mais eficiente para datasets menores")
print("\nPara casos específicos:")
print(" - Alta dimensionalidade: 'svd' ou 'lsqr' com shrinkage")
print(" - Poucas amostras: 'svd' (evita problemas de singularidade)")
print(" - Datasets grandes: 'lsqr' (escalabilidade)")
print(" - Precisão máxima: testar múltiplos solvers com validação cruzada")
'''
Análise das propriedades dos solvers
'''
print("\n6. PROPRIEDADES DOS SOLVERS")
propriedades = {
'svd': {
'shrinkage': 'Não suportado',
'estabilidade': 'Alta',
'dimensionalidade': 'Alta compatibilidade',
'casos_especiais': 'Excelente para matrizes singulares'
},
'lsqr': {
'shrinkage': 'Suportado',
'estabilidade': 'Média-Alta',
'dimensionalidade': 'Boa escalabilidade',
'casos_especiais': 'Bom com regularização'
},
'eigen': {
'shrinkage': 'Suportado',
'estabilidade': 'Média',
'dimensionalidade': 'Limitada por p³',
'casos_especiais': 'Sensível a mal-condicionamento'
}
}
for solver, props in propriedades.items():
print(f"\n{solver.upper()}:")
for prop, valor in props.items():
print(f" {prop}: {valor}")