Machine Learning com Regressão Logística em Python
Aprenda como a regressão logística funciona, como treinar classificadores binários e multiclasse em Python com scikit-learn e ajustar a regularização.
A regressão logística é um algoritmo de classificação supervisionada que estima a probabilidade de uma amostra pertencer a uma determinada classe. Apesar da palavra "regressão" no nome, ela é usada para tarefas de classificação — prever um rótulo categórico como spam/não-spam, doença/saudável ou clique/sem-clique.
Este capítulo aborda:
- Como a regressão logística funciona (a função sigmoide e o log-odds)
- Construção de um classificador binário em Python com
scikit-learn - Avaliação de um classificador além da acurácia bruta
- Tratamento de problemas multiclasse
- Regularização e quando ajustá-la
- Escalonamento de features e por que isso importa
- Quando usar regressão logística em vez de outros classificadores
Como a Regressão Logística Funciona
Da Regressão Linear a Probabilidades
A regressão linear prevê um valor contínuo. Se você tentasse usá-la para classificação, as previsões poderiam cair fora do intervalo [0, 1], tornando-as impossíveis de interpretar como probabilidades. A regressão logística resolve isso passando a combinação linear pela função sigmoide:
σ(z) = 1 / (1 + e^(-z))Onde z = w₀ + w₁x₁ + w₂x₂ + … + wₙxₙ é a soma ponderada das features de entrada. A sigmoide comprime qualquer número real para o intervalo (0, 1), fornecendo uma estimativa de probabilidade válida.
Fronteira de Decisão
O modelo prevê a classe 1 quando a probabilidade excede um limiar (padrão 0,5) e a classe 0 caso contrário:
ŷ = 1 if σ(z) ≥ 0.5
ŷ = 0 if σ(z) < 0.5O limiar σ(z) = 0.5 corresponde a z = 0, o que define a fronteira de decisão — um hiperplano no espaço de features que separa as duas classes.
Log-Odds (Logit)
Calcular o logaritmo da razão de chances mostra por que o modelo é linear nos parâmetros:
log(p / (1 - p)) = w₀ + w₁x₁ + … + wₙxₙCada coeficiente wᵢ representa a mudança no log-odds para um aumento de uma unidade na feature xᵢ, mantendo as demais features constantes. Isso torna a regressão logística interpretável.
Como os Parâmetros São Aprendidos
Ao contrário da regressão linear, não existe solução de forma fechada. O modelo minimiza a função de custo log-loss (entropia cruzada) usando um otimizador iterativo (por padrão lbfgs no scikit-learn):
Loss = -1/m Σ [ yᵢ log(p̂ᵢ) + (1 - yᵢ) log(1 - p̂ᵢ) ]Um log-loss menor significa que as probabilidades previstas estão melhor calibradas em relação aos rótulos verdadeiros.
Classificação Binária em Python
O exemplo a seguir usa o conjunto de dados Breast Cancer Wisconsin — 569 amostras, 30 features numéricas, alvo binário (maligno = 1, benigno = 0). Ele é distribuído com o scikit-learn, portanto nenhum arquivo externo é necessário.
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report
# 1. Load data
data = load_breast_cancer()
X, y = data.data, data.target # X: (569, 30) y: 0=malignant, 1=benign
# 2. Split into train (80 %) and test (20 %)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. Scale features — logistic regression converges faster and more reliably
# when features are on a similar scale
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
# 4. Train
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(X_train_sc, y_train)
# 5. Evaluate
y_pred = clf.predict(X_test_sc)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
print(classification_report(y_test, y_pred, target_names=data.target_names))Saída esperada:
Accuracy: 0.974
precision recall f1-score support
malignant 0.98 0.95 0.96 43
benign 0.97 0.99 0.98 71
accuracy 0.97 114
macro avg 0.97 0.97 0.97 114
weighted avg 0.97 0.97 0.97 114O modelo atinge ~97% de acurácia nos dados de teste. Note que o LogisticRegression do scikit-learn adiciona regularização L2 por padrão (C=1.0), o que auxilia a generalização.
Por Que o Escalonamento de Features Importa
A regressão logística usa otimização baseada em gradiente. Sem escalonamento, uma feature com valores grandes (por exemplo, raio médio ~14) domina as atualizações do gradiente, retardando a convergência ou fazendo o solver falhar. O StandardScaler transforma cada feature para média zero e variância unitária.
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Without scaling — needs more iterations, may warn about convergence
clf_raw = LogisticRegression(max_iter=200, random_state=42)
clf_raw.fit(X_train, y_train)
print(f"Unscaled accuracy : {clf_raw.score(X_test, y_test):.3f}")
# With scaling
scaler = StandardScaler()
clf_sc = LogisticRegression(max_iter=200, random_state=42)
clf_sc.fit(scaler.fit_transform(X_train), y_train)
print(f"Scaled accuracy : {clf_sc.score(scaler.transform(X_test), y_test):.3f}")Sempre ajuste o scaler somente no conjunto de treino e use o mesmo scaler ajustado para transformar tanto o treino quanto o teste — isso evita vazamento de dados.
Avaliando um Classificador
A acurácia isolada pode ser enganosa quando as classes estão desbalanceadas. Use uma matriz de confusão e as métricas derivadas dela.
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(X_train_sc, y_train)
y_pred = clf.predict(X_test_sc)
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(cm, display_labels=data.target_names)
disp.plot(cmap="Blues")
plt.title("Logistic Regression — Breast Cancer")
plt.tight_layout()
plt.savefig("confusion_matrix.png", dpi=150)
plt.show()Principais métricas derivadas da matriz de confusão:
| Métrica | Fórmula | Significado |
|---|---|---|
| Precisão | TP / (TP + FP) | De todos os positivos previstos, quantos são realmente positivos |
| Recall (Sensibilidade) | TP / (TP + FN) | De todos os positivos reais, quantos o modelo identificou |
| F1-score | 2 × (P × R) / (P + R) | Média harmônica de precisão e recall |
| Especificidade | TN / (TN + FP) | De todos os negativos reais, quantos o modelo rejeitou corretamente |
No diagnóstico médico, o recall (sensibilidade) costuma ser mais importante que a precisão — uma malignidade não detectada é pior do que um falso alarme.
Pontuações de Probabilidade e a Curva AUC-ROC
Em vez de uma previsão definitiva, você pode obter a probabilidade da classe positiva com predict_proba():
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(scaler.fit_transform(X_train), y_train)
# Probability of the positive class (benign = 1)
y_proba = clf.predict_proba(scaler.transform(X_test))[:, 1]
print(f"AUC-ROC: {roc_auc_score(y_test, y_proba):.3f}")Saída esperada:
AUC-ROC: 0.997Um AUC próximo de 1,0 significa que o modelo classifica as amostras positivas acima das negativas quase perfeitamente. Consulte o capítulo Curva AUC-ROC para saber como plotar e interpretar a curva completa.
Classificação Multiclasse
Quando o alvo possui mais de duas classes, o scikit-learn estende a regressão logística automaticamente. A partir do scikit-learn 1.5, o solver lbfgs sempre utiliza a abordagem multinomial (softmax), que treina um único modelo com uma camada de saída softmax e minimiza a entropia cruzada sobre todas as classes em conjunto. Isso geralmente é mais preciso do que a antiga estratégia One-vs-Rest (OvR), que treinava um classificador binário separado por classe.
O conjunto de dados Iris possui três espécies de flores — um exemplo natural de multiclasse:
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
data = load_iris()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_sc = scaler.fit_transform(X_train)
X_test_sc = scaler.transform(X_test)
# From scikit-learn 1.5+, multinomial softmax is the default for lbfgs
clf = LogisticRegression(solver="lbfgs", max_iter=1000, random_state=42)
clf.fit(X_train_sc, y_train)
y_pred = clf.predict(X_test_sc)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
# Class probabilities for the first three test samples
print("\nClass probabilities (first 3 samples):")
for proba in clf.predict_proba(X_test_sc)[:3]:
print([f"{p:.3f}" for p in proba])Saída esperada:
Accuracy: 1.000
Class probabilities (first 3 samples):
['0.011', '0.876', '0.113']
['0.964', '0.036', '0.000']
['0.000', '0.003', '0.997']Regularização
A regularização penaliza coeficientes grandes para evitar overfitting. O scikit-learn oferece dois tipos por meio do parâmetro penalty:
| Parâmetro | Tipo | Efeito |
|---|---|---|
penalty='l2' (padrão) | Ridge | Encolhe todos os coeficientes em direção a zero; mantém todas as features |
penalty='l1' | Lasso | Leva alguns coeficientes exatamente a zero; seleção implícita de features |
penalty='elasticnet' | Misto | Combina L1 e L2; requer solver='saga' |
penalty=None | Nenhum | Sem regularização; use somente se os dados forem grandes e limpos |
A intensidade da regularização é controlada por C (o inverso da intensidade de regularização — um C menor significa regularização mais forte):
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
data = load_breast_cancer()
X, y = data.data, data.target
results = {}
for C in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]:
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(C=C, max_iter=1000, random_state=42)),
])
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
results[C] = scores.mean()
print(f"C={C:7.3f} CV accuracy: {scores.mean():.4f} ± {scores.std():.4f}")Isso usa validação cruzada para encontrar o valor de C que melhor generaliza. Para uma busca sistemática sobre múltiplos hiperparâmetros, consulte Grid Search.
Usando um Pipeline
Um Pipeline encadeia o pré-processamento e o modelo em um único objeto. Isso evita vazamento acidental de dados e simplifica a validação cruzada e a implantação:
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(C=1.0, max_iter=1000, random_state=42)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
# Predict probabilities on a new sample (raw, unscaled)
new_sample = X_test[:1] # first test sample
print(f"Predicted class : {pipe.predict(new_sample)[0]}")
print(f"Class probability : {pipe.predict_proba(new_sample)[0]}")Saída esperada:
Accuracy: 0.974
Predicted class : 1
Class probability : [0.11359025 0.88640975]O pipeline lida com o escalonamento internamente — você chama predict() com os valores brutos das features.
Inspecionando os Coeficientes
Os coeficientes treinados revelam quais features empurram a previsão em direção a cada classe. Valores absolutos maiores significam influência mais forte:
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import numpy as np
data = load_breast_cancer()
X, y = data.data, data.target
scaler = StandardScaler()
clf = LogisticRegression(max_iter=1000, random_state=42)
clf.fit(scaler.fit_transform(X), y)
# Sort by absolute coefficient value
coefs = clf.coef_[0] # shape (n_features,) for binary classification
sorted_idx = np.argsort(np.abs(coefs))[::-1]
print(f"{'Feature':<35} {'Coefficient':>12}")
print("-" * 48)
for i in sorted_idx[:5]:
print(f"{data.feature_names[i]:<35} {coefs[i]:>12.4f}")Saída esperada (top 5 features por peso absoluto):
Feature Coefficient
------------------------------------------------
worst texture -1.3206
radius error -1.2893
worst radius -1.0266
area error -0.9989
worst area -0.9947Coeficientes negativos (após escalonamento) empurram em direção à classe 0 (maligno); coeficientes positivos empurram em direção à classe 1 (benigno).
Vantagens e Limitações
Quando usar regressão logística
- Você precisa de estimativas de probabilidade, não apenas de rótulos de classe.
- A relação entre as features e o log-odds é aproximadamente linear.
- Você precisa de um modelo interpretável — os coeficientes são significativos.
- Como uma linha de base rápida antes de experimentar modelos mais complexos como Árvores de Decisão ou métodos de ensemble.
- Os conjuntos de dados são grandes (a regressão logística escala bem com muitas amostras).
Limitações
| Limitação | Mitigação |
|---|---|
| Assume uma fronteira de decisão linear | Use features polinomiais ou mude para Árvore de Decisão / K-Nearest Neighbors |
| Sensível à escala das features | Sempre aplique StandardScaler ou MinMaxScaler |
| Dificuldades com features altamente correlacionadas | Elimine ou regularize com L1 (penalty='l1') |
| Não indicada para interações de features muito complexas | Use métodos de ensemble ou redes neurais |
Regressão Logística vs. Classificadores Relacionados
| Algoritmo | Fronteira de decisão | Escalonamento necessário | Saída probabilística |
|---|---|---|---|
| Regressão Logística | Linear | Sim | Sim (calibrada) |
| Árvore de Decisão | Não linear (alinhada aos eixos) | Não | Sim (menos calibrada) |
| K-Nearest Neighbors | Não linear (baseada em instâncias) | Sim | Sim |
| Regressão Linear | Linear (saída contínua) | Sim | Não |
Principais Conclusões
- A regressão logística estima uma probabilidade usando a função sigmoide; a classe é atribuída pelo limiar dessa probabilidade.
- Sempre escalone as features com
StandardScalerantes do treino — isso acelera a convergência e melhora a acurácia. - Use um Pipeline para agrupar o escalonamento e o modelo; ele previne vazamento de dados e simplifica a implantação.
- Avalie com precisão, recall, F1 e AUC-ROC em vez de apenas acurácia, especialmente com dados desbalanceados. Consulte os capítulos Matriz de Confusão e Curva AUC-ROC.
- Controle o overfitting com o parâmetro
C(menor = regularização mais forte); use validação cruzada ou grid search para ajustá-lo. - Para problemas multiclasse, use
solver="lbfgs"(o padrão); o scikit-learn 1.5+ sempre usa softmax (multinomial), que lida bem com classes sobrepostas.