Mais features nem sempre é melhor. Acima de certo ponto, cada variável extra adiciona ruído, custo computacional, risco de overfitting e dificuldade de interpretação — a famosa maldição da dimensionalidade. Esta aula é sobre o movimento oposto: depois de criar muitas features, como escolher as que importam e como representar os dados em menos dimensões sem perder sinal.

Compreender a maldição da dimensionalidade, dominar os três paradigmas de seleção de características (filtros, wrappers, embedded) e as principais técnicas de redução de dimensionalidade (PCA, e não-lineares como t-SNE/UMAP), sabendo quando cada abordagem é apropriada.

A maldição da dimensionalidade

Em altas dimensões, o volume do espaço cresce exponencialmente, os pontos ficam esparsos e "todo mundo fica longe de todo mundo" — noções de vizinhança e distância perdem significado. Consequências: modelos precisam de muito mais dados, o risco de overfitting dispara e o custo cresce. Seleção e redução combatem isso.

Seleção vs. redução — a distinção fundamental

  • Seleção de características: escolhe um subconjunto das features originais, descartando o resto. Mantém interpretabilidade (as features sobreviventes são as originais).
  • Redução de dimensionalidade: cria novas features (combinações das originais) em um espaço menor. Perde interpretabilidade direta, mas pode captar estrutura melhor.

Métodos de filtro (filter)

Avaliam cada feature (ou pares) por uma estatística, independentemente do modelo. Rápidos, escaláveis, mas ignoram interações entre features e a interação com o modelo.

from sklearn.feature_selection import (SelectKBest, f_classif,
                                       mutual_info_classif, VarianceThreshold)
from sklearn.datasets import make_classification
import numpy as np

X, y = make_classification(n_samples=2000, n_features=50, n_informative=10,
                           n_redundant=10, random_state=42)

# Remover features de variância quase nula
X_var = VarianceThreshold(threshold=0.01).fit_transform(X)

# ANOVA F-test: relação linear com o alvo
kbest = SelectKBest(score_func=f_classif, k=10).fit(X, y)
print("Features F-test:", np.where(kbest.get_support())[0])

# Informação mútua: captura relações não-lineares
mi = mutual_info_classif(X, y, random_state=42)
print("Top 10 por MI:", np.argsort(mi)[-10:])
  • Variância: remove constantes/quase-constantes.
  • Correlação/ANOVA F: relação (linear) feature-alvo.
  • Informação mútua: relação não-linear; não assume linearidade.
  • Correlação entre features: remova uma de cada par altamente correlacionado (redundância).

Métodos wrapper

Usam o próprio modelo para avaliar subconjuntos de features, treinando repetidamente. Capturam interações e a afinidade com o modelo, mas são caros.

from sklearn.feature_selection import RFE, RFECV
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold

# RFE: elimina recursivamente a feature menos importante
rfe = RFE(RandomForestClassifier(n_estimators=100, random_state=42),
          n_features_to_select=10).fit(X, y)
print("RFE selecionou:", np.where(rfe.support_)[0])

# RFECV: escolhe o número ótimo de features por validação cruzada
rfecv = RFECV(RandomForestClassifier(n_estimators=100, random_state=42),
              cv=StratifiedKFold(5), scoring="roc_auc").fit(X, y)
print(f"Número ótimo de features: {rfecv.n_features_}")
  • Forward selection: começa vazio e adiciona a melhor feature a cada passo.
  • Backward elimination: começa com todas e remove a pior.
  • RFE: elimina recursivamente pela importância; RFECV escolhe o número de features por CV.

Cuidado: como treinam muitas vezes nos mesmos dados, wrappers podem overfittar a seleção — sempre avalie o resultado final em validação externa (nested CV, Módulo 5).

Métodos embedded

A seleção acontece durante o treino do modelo. Melhor custo-benefício: um único treino já seleciona.

Regularização L1 (Lasso)

A penalidade L1 zera coeficientes de features irrelevantes, fazendo seleção automática.

from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import SelectFromModel

lasso = LogisticRegression(penalty="l1", solver="liblinear", C=0.1)
sel = SelectFromModel(lasso).fit(X, y)
print("Lasso manteve:", np.sum(sel.get_support()), "features")

L1 (Lasso) força esparsidade; L2 (Ridge) encolhe mas não zera; Elastic Net combina os dois.

Importância de árvores

Modelos de árvore fornecem importâncias que podem guiar a seleção — mas com ressalvas importantes.

rf = RandomForestClassifier(n_estimators=300, random_state=42).fit(X, y)
sel = SelectFromModel(rf, threshold="median").fit(X, y)   # mantém acima da mediana

Alerta: a importância por impureza (feature_importances_) é enviesada para features de alta cardinalidade e correlacionadas. Prefira permutation importance (Módulo 8) para decisões mais confiáveis.

Redução de dimensionalidade linear: PCA

PCA encontra as direções (componentes principais) de máxima variância e projeta os dados nelas. Componentes são ortogonais e ordenados por variância explicada.

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np

# PCA exige dados padronizados (é sensível à escala)
pipe = Pipeline([("scaler", StandardScaler()), ("pca", PCA())])
pipe.fit(X)

var_exp = pipe.named_steps["pca"].explained_variance_ratio_
var_cum = np.cumsum(var_exp)
n_90 = np.argmax(var_cum >= 0.90) + 1
print(f"Componentes para 90% da variância: {n_90} (de {X.shape[1]})")

# Reduzir mantendo 95% da variância
pca_95 = PCA(n_components=0.95).fit_transform(StandardScaler().fit_transform(X))
print(f"Shape após PCA: {pca_95.shape}")

Pontos-chave: padronize antes (PCA é sensível à escala); componentes são combinações lineares (perdem interpretabilidade direta); use a curva de variância acumulada (scree plot) para escolher o número de componentes. PCA é não-supervisionado — não usa o alvo.

Redução não-linear: t-SNE e UMAP

Quando a estrutura dos dados é não-linear, PCA não a captura. t-SNE e UMAP preservam estrutura local e são excelentes para visualização em 2D/3D.

from sklearn.manifold import TSNE
# import umap  # pip install umap-learn

X_tsne = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X)
# X_umap = umap.UMAP(n_components=2, random_state=42).fit_transform(X)

Advertências cruciais: t-SNE e UMAP são para visualização e exploração, não para gerar features de treino de forma ingênua. As distâncias globais em t-SNE não são confiáveis; a perplexity muda drasticamente o resultado; e reaplicar a novos pontos é problemático (t-SNE não tem transform estável). UMAP preserva melhor a estrutura global e escala melhor.

Estratégia prática de seleção

Não escolha um método isolado — combine em pipeline, do mais barato ao mais caro:

  1. Remova constantes e quase-constantes (variância).
  2. Remova redundância (correlação alta entre features).
  3. Aplique um filtro (MI ou F-test) para cortar o volume.
  4. Refine com embedded (L1 ou importância por permutação).
  5. Valide o subconjunto final em CV externa, e — se estiver ajustando quantas features manter — dentro de nested CV (Módulo 5), pois a própria seleção é uma decisão que pode overfittar.
# Em R (tidymodels) — seleção via recipe
library(tidymodels)
receita <- recipe(alvo ~ ., data = treino) |>
  step_zv(all_predictors()) |>                    # zero variance
  step_corr(all_numeric_predictors(), threshold = 0.9) |>  # remove correlacionadas
  step_normalize(all_numeric_predictors()) |>
  step_pca(all_numeric_predictors(), threshold = 0.95)     # PCA 95% variância

Em altas dimensões os dados ficam esparsos e os modelos passam a decorar em vez de aprender; seleção e redução são as duas respostas para isso, e elas não são a mesma coisa. A seleção mantém as features originais, o que preserva a interpretação; a redução cria features novas, mais compactas e mais opacas. Dentro da seleção há três famílias: os filtros são rápidos e independentes do modelo — variância, F-test, informação mútua —; os wrappers usam o próprio modelo e capturam interações, ao custo de ficarem caros, como o RFE e o RFECV; e os embedded selecionam durante o treino, caso do Lasso e da importância por árvore. Sobre essa importância, um cuidado: a medida por impureza é enviesada, e a permutation importance é a alternativa honesta. Do lado da redução, o PCA é linear, exige padronizar antes e preserva variância; t-SNE e UMAP são não lineares e servem para visualizar, não para alimentar treino ingenuamente. Por fim, combine os métodos dentro do pipeline e valide a seleção em validação cruzada externa ou aninhada — selecionar fora dela é vazar.

Exercícios

  1. Qual a diferença fundamental entre seleção de características e redução de dimensionalidade, e qual delas preserva a interpretabilidade?

    ✓ Resposta:

    Seleção de características escolhe um subconjunto das features originais, descartando as demais — as variáveis que sobrevivem continuam sendo as originais, então a interpretabilidade é preservada (você sabe exatamente o que cada feature significa). Redução de dimensionalidade cria novas features que são combinações (lineares em PCA, não-lineares em UMAP) das originais, projetando os dados em um espaço menor; ganha-se compactação e possível captura de estrutura, mas cada nova dimensão é uma mistura das antigas, dificultando a interpretação direta. Portanto, a seleção preserva a interpretabilidade.

  2. Explique a diferença entre métodos filter, wrapper e embedded, com um exemplo de cada e uma vantagem/desvantagem.

    ✓ Resposta:

    Filter avalia features por uma estatística independente do modelo (ex.: informação mútua). Vantagem: rápido e escalável. Desvantagem: ignora interações entre features e a afinidade com o modelo. Wrapper usa o próprio modelo para avaliar subconjuntos, treinando repetidamente (ex.: RFE). Vantagem: captura interações e adapta-se ao modelo. Desvantagem: caro e propenso a overfittar a seleção. Embedded seleciona durante o treino do modelo (ex.: Lasso/L1). Vantagem: bom custo-benefício, um único treino já seleciona e considera o modelo. Desvantagem: atrelado a um tipo específico de modelo/penalização.

  3. Por que é obrigatório padronizar os dados antes de aplicar PCA?

    ✓ Resposta:

    PCA busca as direções de máxima variância. Como a variância depende da escala das variáveis, uma feature medida em unidades grandes (ex.: renda em milhares) dominaria a variância total e os primeiros componentes, apenas por causa da escala, e não por conter mais informação. Padronizar (z-score) coloca todas as features em variância unitária, garantindo que o PCA capture a estrutura de correlação real e não artefatos de unidade. Sem padronizar, o resultado do PCA é essencialmente arbitrário em datasets com escalas heterogêneas.

  4. Por que t-SNE e UMAP não devem ser usados ingenuamente para gerar features de entrada de um modelo de produção?

    ✓ Resposta:

    (a) São feitos para preservar estrutura local para visualização; distâncias globais (especialmente em t-SNE) não são confiáveis. (b) t-SNE não fornece um transform estável para novos pontos — reprojetar dados de produção que não estavam no ajuste é problemático; refazer o embedding muda a representação. (c) Resultados dependem fortemente de hiperparâmetros (perplexity, n_neighbors), tornando a feature instável e não reprodutível entre execuções. (d) O risco de vazamento e de instabilidade compromete a validação. UMAP é mais robusto e tem transform, mas ainda exige cuidado. Para features de produção, PCA (com transform determinístico) ou seleção supervisionada são mais seguros.

  5. Você tem 200 features, muitas correlacionadas. Descreva uma estratégia em etapas para reduzir esse conjunto sem vazamento.

    ✓ Resposta:

    (1) Remover features de variância ~zero. (2) Detectar pares altamente correlacionados (|r| > 0.9) e descartar um de cada par (reduz redundância). (3) Aplicar um filtro barato (informação mútua ou F-test) para reduzir o volume, ou uma penalização L1 (embedded) para selecionar. (4) Opcionalmente, aplicar PCA para compactar ainda mais mantendo ~95% da variância. Crucialmente, todas essas etapas devem estar dentro de um Pipeline/recipe e serem reajustadas apenas no treino de cada fold durante a validação. Se o número de features a manter for um hiperparâmetro sintonizado, use nested CV para que a seleção não vaze na estimativa de desempenho final.

Fontes e leituras recomendadas