W3docs

Machine Learning con la Regressione Logistica in Python

Scopri come funziona la regressione logistica, come addestrare classificatori binari e multiclasse in Python con scikit-learn, valutarli e regolare la regolarizzazione.

La regressione logistica è un algoritmo di classificazione supervisionata che stima la probabilità che un campione appartenga a una determinata classe. Nonostante la parola "regressione" nel nome, viene utilizzata per compiti di classificazione — prevedere un'etichetta categorica come spam/non-spam, malattia/sano o clic/nessun-clic.

Questo capitolo tratta:

  • Come funziona la regressione logistica (la funzione sigmoid e il log-odds)
  • Costruire un classificatore binario in Python con scikit-learn
  • Valutare un classificatore oltre alla semplice accuratezza
  • Gestire problemi multiclasse
  • La regolarizzazione e quando modificarla
  • Il ridimensionamento delle feature e perché è importante
  • Quando usare la regressione logistica rispetto ad altri classificatori

Come Funziona la Regressione Logistica

Da Regressione Lineare alle Probabilità

La regressione lineare prevede un valore continuo. Se si provasse a usarla per la classificazione, le previsioni potrebbero cadere al di fuori di [0, 1], rendendo impossibile interpretarle come probabilità. La regressione logistica risolve questo problema passando la combinazione lineare attraverso la funzione sigmoid:

σ(z) = 1 / (1 + e^(-z))

Dove z = w₀ + w₁x₁ + w₂x₂ + … + wₙxₙ è la somma ponderata delle feature di input. La sigmoid comprime qualsiasi numero reale nell'intervallo (0, 1), fornendo una stima di probabilità valida.

Confine di Decisione

Il modello predice la classe 1 quando la probabilità supera una soglia (predefinita 0.5), e la classe 0 altrimenti:

ŷ = 1  if σ(z) ≥ 0.5
ŷ = 0  if σ(z) < 0.5

La soglia σ(z) = 0.5 corrisponde a z = 0, che definisce il confine di decisione — un iperpiano nello spazio delle feature che separa le due classi.

Log-Odds (Logit)

Prendendo il logaritmo del rapporto delle probabilità si mostra perché il modello è lineare nei parametri:

log(p / (1 - p)) = w₀ + w₁x₁ + … + wₙxₙ

Ogni coefficiente wᵢ rappresenta la variazione del log-odds per un aumento di un'unità nella feature xᵢ, mantenendo costanti le altre feature. Questo rende la regressione logistica interpretabile.

Come Vengono Appresi i Parametri

A differenza della regressione lineare, non esiste una soluzione in forma chiusa. Il modello minimizza la funzione di costo log-loss (entropia incrociata) utilizzando un ottimizzatore iterativo (di default lbfgs in scikit-learn):

Loss = -1/m Σ [ yᵢ log(p̂ᵢ) + (1 - yᵢ) log(1 - p̂ᵢ) ]

Una log-loss inferiore significa che le probabilità predette sono meglio calibrate rispetto alle etichette reali.

Classificazione Binaria in Python

L'esempio seguente utilizza il dataset Breast Cancer Wisconsin — 569 campioni, 30 feature numeriche, target binario (maligno = 1, benigno = 0). È incluso in scikit-learn, quindi non sono necessari file esterni.

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))

Output atteso:

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       114

Il modello raggiunge circa il 97% di accuratezza sui dati non visti. Si noti che LogisticRegression di scikit-learn aggiunge di default la regolarizzazione L2 (C=1.0), che aiuta la generalizzazione.

Perché il Ridimensionamento delle Feature è Importante

La regressione logistica utilizza un'ottimizzazione basata sul gradiente. Senza ridimensionamento, una feature con valori grandi (ad es., raggio medio ~14) domina gli aggiornamenti del gradiente, rallentando la convergenza o causando il fallimento del solver. StandardScaler trasforma ogni feature con media zero e varianza unitaria.

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}")

Addestrare sempre lo scaler solo sul set di addestramento, quindi usare lo stesso scaler già adattato per trasformare sia il set di addestramento che quello di test — questo previene il data leakage.

Valutare un Classificatore

L'accuratezza da sola può essere fuorviante quando le classi sono sbilanciate. Usa una matrice di confusione e le metriche derivate da essa.

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()

Metriche chiave dalla matrice di confusione:

MetricaFormulaSignificato
PrecisioneTP / (TP + FP)Di tutti i positivi previsti, quanti sono davvero positivi
Recall (Sensibilità)TP / (TP + FN)Di tutti i positivi reali, quanti il modello ha rilevato
F1-score2 × (P × R) / (P + R)Media armonica di precisione e recall
SpecificitàTN / (TN + FP)Di tutti i negativi reali, quanti il modello ha correttamente escluso

Nella diagnosi medica, il recall (sensibilità) è spesso più importante della precisione — un tumore maligno non rilevato è peggio di un falso allarme.

Punteggi di Probabilità e la Curva AUC-ROC

Invece di una previsione definitiva, puoi recuperare la probabilità della classe positiva con 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}")

Output atteso:

AUC-ROC: 0.997

Un AUC vicino a 1.0 significa che il modello classifica i campioni positivi sopra quelli negativi quasi perfettamente. Consulta il capitolo Curva AUC-ROC per come tracciare e interpretare la curva completa.

Classificazione Multiclasse

Quando il target ha più di due classi, scikit-learn estende automaticamente la regressione logistica. Da scikit-learn 1.5 in poi, il solver lbfgs usa sempre l'approccio multinomiale (softmax), che addestra un singolo modello con uno strato di output softmax e minimizza l'entropia incrociata su tutte le classi congiuntamente. Questo è generalmente più accurato della vecchia strategia One-vs-Rest (OvR), che addestrava un classificatore binario separato per ogni classe.

Il dataset Iris ha tre specie di fiori — un esempio naturale di classificazione 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])

Output atteso:

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']

Regolarizzazione

La regolarizzazione penalizza i coefficienti grandi per prevenire l'overfitting. scikit-learn fornisce due tipi tramite il parametro penalty:

ParametroTipoEffetto
penalty='l2' (default)RidgeRiduce tutti i coefficienti verso zero; mantiene tutte le feature
penalty='l1'LassoPorta alcuni coefficienti esattamente a zero; selezione implicita delle feature
penalty='elasticnet'MixCombina L1 e L2; richiede solver='saga'
penalty=NoneNessunaNessuna regolarizzazione; da usare solo se i dati sono grandi e puliti

La forza della regolarizzazione è controllata da C (l'inverso della forza di regolarizzazione — un C più piccolo significa una regolarizzazione più 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}")

Questo utilizza la cross-validation per trovare il valore di C che generalizza meglio. Per una ricerca sistematica su più iperparametri, consulta Grid Search.

Usare una Pipeline

Una Pipeline concatena la pre-elaborazione e il modello in un singolo oggetto. Questo previene il data leakage accidentale e semplifica la cross-validation e il deployment:

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]}")

Output atteso:

Accuracy: 0.974
Predicted class   : 1
Class probability : [0.11359025 0.88640975]

La pipeline gestisce il ridimensionamento internamente — si chiama predict() con i valori delle feature grezze.

Ispezionare i Coefficienti

I coefficienti addestrati rivelano quali feature spingono la previsione verso ogni classe. Valori assoluti più grandi significano un'influenza più 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}")

Output atteso (prime 5 feature per peso assoluto):

Feature                              Coefficient
------------------------------------------------
worst texture                            -1.3206
radius error                             -1.2893
worst radius                             -1.0266
area error                               -0.9989
worst area                               -0.9947

I coefficienti negativi (dopo il ridimensionamento) spingono verso la classe 0 (maligno); i coefficienti positivi spingono verso la classe 1 (benigno).

Vantaggi e Limitazioni

Quando usare la regressione logistica

  • Hai bisogno di stime di probabilità, non solo etichette di classe.
  • La relazione tra le feature e il log-odds è approssimativamente lineare.
  • Hai bisogno di un modello interpretabile — i coefficienti sono significativi.
  • Come baseline veloce prima di provare modelli più complessi come gli Alberi di Decisione o i metodi ensemble.
  • I dataset sono grandi (la regressione logistica scala bene con molti campioni).

Limitazioni

LimitazioneMitigazione
Assume un confine di decisione lineareUsa le feature polinomiali o passa all'Albero di Decisione / K-Nearest Neighbors
Sensibile alla scala delle featureApplica sempre StandardScaler o MinMaxScaler
Difficoltà con feature altamente correlateElimina o regolarizza con L1 (penalty='l1')
Non adatta per interazioni tra feature molto complesseUsa metodi ensemble o reti neurali

Regressione Logistica vs. Classificatori Correlati

AlgoritmoConfine di decisioneScaling necessarioOutput probabilistico
Regressione LogisticaLineareSì (calibrato)
Albero di DecisioneNon lineare (allineato agli assi)NoSì (meno calibrato)
K-Nearest NeighborsNon lineare (basato sulle istanze)
Regressione LineareLineare (output continuo)No

Punti Chiave

  • La regressione logistica stima una probabilità usando la funzione sigmoid; la classe viene assegnata sogliando quella probabilità.
  • Ridimensiona sempre le feature con StandardScaler prima dell'addestramento — accelera la convergenza e migliora l'accuratezza.
  • Usa una Pipeline per raggruppare il ridimensionamento e il modello; previene il data leakage e semplifica il deployment.
  • Valuta con precisione, recall, F1 e AUC-ROC piuttosto che con la sola accuratezza, specialmente con dati sbilanciati. Consulta i capitoli sulla Matrice di Confusione e sulla Curva AUC-ROC.
  • Controlla l'overfitting con il parametro C (più piccolo = regolarizzazione più forte); usa la cross-validation o la grid search per ottimizzarlo.
  • Per i problemi multiclasse, usa solver="lbfgs" (il default); scikit-learn 1.5+ usa sempre softmax (multinomiale) che gestisce bene le classi sovrapposte.
Was this page helpful?