Машинное обучение с логистической регрессией в Python
Узнайте, как работает логистическая регрессия, как обучать бинарные и многоклассовые классификаторы в Python с scikit-learn и настраивать регуляризацию.
Логистическая регрессия — это алгоритм классификации с учителем, оценивающий вероятность принадлежности образца к определённому классу. Несмотря на слово «регрессия» в названии, он применяется для задач классификации — предсказания категориального метки, например: спам/не-спам, болезнь/здоров, клик/нет клика.
В этой главе рассматривается:
- Как работает логистическая регрессия (сигмоидная функция и логарифм шансов)
- Построение бинарного классификатора в Python с помощью
scikit-learn - Оценка классификатора помимо простой точности
- Обработка многоклассовых задач
- Регуляризация и когда её корректировать
- Масштабирование признаков и почему это важно
- Когда использовать логистическую регрессию, а не другие классификаторы
Как работает логистическая регрессия
От линейной регрессии к вероятностям
Линейная регрессия предсказывает непрерывное значение. При использовании её для классификации предсказания могут выходить за пределы [0, 1], что делает их невозможными для интерпретации как вероятности. Логистическая регрессия решает эту проблему, пропуская линейную комбинацию через сигмоидную функцию:
σ(z) = 1 / (1 + e^(-z))Где z = w₀ + w₁x₁ + w₂x₂ + … + wₙxₙ — взвешенная сумма входных признаков. Сигмоид сжимает любое вещественное число в диапазон (0, 1), давая корректную оценку вероятности.
Граница принятия решений
Модель предсказывает класс 1, когда вероятность превышает порог (по умолчанию 0.5), и класс 0 в противном случае:
ŷ = 1 if σ(z) ≥ 0.5
ŷ = 0 if σ(z) < 0.5Порог σ(z) = 0.5 соответствует z = 0, что определяет границу принятия решений — гиперплоскость в пространстве признаков, разделяющую два класса.
Логарифм шансов (логит)
Взяв логарифм отношения шансов, можно показать, почему модель линейна по параметрам:
log(p / (1 - p)) = w₀ + w₁x₁ + … + wₙxₙКаждый коэффициент wᵢ отражает изменение логарифма шансов при увеличении признака xᵢ на одну единицу при фиксированных остальных признаках. Это делает логистическую регрессию интерпретируемой.
Как обучаются параметры
В отличие от линейной регрессии, здесь нет аналитического решения. Модель минимизирует функцию потерь log-loss (перекрёстная энтропия) с помощью итерационного оптимизатора (по умолчанию lbfgs в scikit-learn):
Loss = -1/m Σ [ yᵢ log(p̂ᵢ) + (1 - yᵢ) log(1 - p̂ᵢ) ]Меньший log-loss означает, что предсказанные вероятности лучше соответствуют истинным меткам.
Бинарная классификация в Python
Следующий пример использует набор данных Breast Cancer Wisconsin — 569 образцов, 30 числовых признаков, бинарная цель (злокачественная = 1, доброкачественная = 0). Он поставляется с scikit-learn, поэтому внешние файлы не нужны.
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))Ожидаемый вывод:
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Модель достигает ~97% точности на отложенных данных. Обратите внимание, что LogisticRegression в scikit-learn по умолчанию добавляет L2-регуляризацию (C=1.0), что помогает обобщению.
Почему масштабирование признаков важно
Логистическая регрессия использует оптимизацию на основе градиента. Без масштабирования признак с большими значениями (например, средний радиус ~14) доминирует в обновлениях градиента, замедляя сходимость или вызывая сбой решателя. StandardScaler преобразует каждый признак к нулевому среднему и единичной дисперсии.
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}")Всегда обучайте скейлер только на обучающей выборке, а затем используйте тот же обученный скейлер для преобразования обучающей и тестовой выборок — это предотвращает утечку данных.
Оценка классификатора
Только точность может вводить в заблуждение при несбалансированных классах. Используйте матрицу ошибок и метрики, производные от неё.
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()Ключевые метрики из матрицы ошибок:
| Метрика | Формула | Значение |
|---|---|---|
| Точность (Precision) | TP / (TP + FP) | Из всех предсказанных положительных сколько действительно положительных |
| Полнота (Recall, Sensitivity) | TP / (TP + FN) | Из всех фактических положительных сколько поймала модель |
| F1-мера (F1-score) | 2 × (P × R) / (P + R) | Гармоническое среднее точности и полноты |
| Специфичность (Specificity) | TN / (TN + FP) | Из всех фактических отрицательных сколько модель правильно отклонила |
В медицинской диагностике полнота (чувствительность) зачастую важнее точности — пропущенное злокачественное образование хуже ложной тревоги.
Вероятностные оценки и кривая AUC-ROC
Вместо жёсткого предсказания можно получить вероятность положительного класса с помощью 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}")Ожидаемый вывод:
AUC-ROC: 0.997AUC, близкий к 1.0, означает, что модель почти идеально ранжирует положительные образцы выше отрицательных. Смотрите главу Кривая AUC-ROC для построения и интерпретации полной кривой.
Многоклассовая классификация
Когда целевая переменная имеет более двух классов, scikit-learn автоматически расширяет логистическую регрессию. Начиная с scikit-learn 1.5, решатель lbfgs всегда использует подход многочленной (softmax) регрессии, обучая единую модель с выходным слоем softmax и минимизируя перекрёстную энтропию по всем классам совместно. Это, как правило, точнее, чем старая стратегия One-vs-Rest (OvR), которая обучала отдельный бинарный классификатор для каждого класса.
Набор данных Iris содержит три вида цветов — естественный пример многоклассовой задачи:
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])Ожидаемый вывод:
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']Регуляризация
Регуляризация штрафует большие коэффициенты для предотвращения переобучения. scikit-learn предоставляет два типа через параметр penalty:
| Параметр | Тип | Эффект |
|---|---|---|
penalty='l2' (по умолчанию) | Ridge | Сжимает все коэффициенты к нулю; сохраняет все признаки |
penalty='l1' | Lasso | Обнуляет некоторые коэффициенты; неявный отбор признаков |
penalty='elasticnet' | Mix | Сочетает L1 и L2; требует solver='saga' |
penalty=None | None | Без регуляризации; используйте только при больших и чистых данных |
Сила регуляризации управляется параметром C (обратная величина силы регуляризации — меньшее C означает более сильную регуляризацию):
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}")Здесь используется кросс-валидация для нахождения значения C, обеспечивающего наилучшее обобщение. Для систематического поиска по нескольким гиперпараметрам смотрите Grid Search.
Использование Pipeline
Pipeline объединяет предобработку и модель в единый объект. Это предотвращает случайную утечку данных и упрощает кросс-валидацию и развёртывание:
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]}")Ожидаемый вывод:
Accuracy: 0.974
Predicted class : 1
Class probability : [0.11359025 0.88640975]Pipeline выполняет масштабирование внутри — вы вызываете predict() с исходными значениями признаков.
Анализ коэффициентов
Обученные коэффициенты показывают, какие признаки толкают предсказание к каждому классу. Большие абсолютные значения означают более сильное влияние:
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}")Ожидаемый вывод (топ-5 признаков по абсолютному весу):
Feature Coefficient
------------------------------------------------
worst texture -1.3206
radius error -1.2893
worst radius -1.0266
area error -0.9989
worst area -0.9947Отрицательные коэффициенты (после масштабирования) толкают к классу 0 (злокачественный); положительные — к классу 1 (доброкачественный).
Преимущества и ограничения
Когда использовать логистическую регрессию
- Вам нужны оценки вероятности, а не только метки классов.
- Связь между признаками и логарифмом шансов примерно линейна.
- Вам нужна интерпретируемая модель — коэффициенты имеют смысл.
- Как быстрый базовый вариант перед переходом к более сложным моделям, таким как Деревья решений или ансамблевые методы.
- Наборы данных большие (логистическая регрессия хорошо масштабируется при большом количестве образцов).
Ограничения
| Ограничение | Способ устранения |
|---|---|
| Предполагает линейную границу принятия решений | Используйте полиномиальные признаки или перейдите к Decision Tree / K-Nearest Neighbors |
| Чувствительна к масштабу признаков | Всегда применяйте StandardScaler или MinMaxScaler |
| Плохо работает с сильно коррелированными признаками | Удалите признаки или используйте L1-регуляризацию (penalty='l1') |
| Не подходит для сложных взаимодействий признаков | Используйте ансамблевые методы или нейронные сети |
Логистическая регрессия vs. связанные классификаторы
| Алгоритм | Граница решений | Нужно масштабирование | Вероятностный вывод |
|---|---|---|---|
| Логистическая регрессия | Линейная | Да | Да (калиброванный) |
| Decision Tree | Нелинейная (по осям) | Нет | Да (менее калиброванный) |
| K-Nearest Neighbors | Нелинейная (на основе экземпляров) | Да | Да |
| Linear Regression | Линейная (непрерывный вывод) | Да | Нет |
Ключевые выводы
- Логистическая регрессия оценивает вероятность с помощью сигмоидной функции; класс присваивается путём сравнения этой вероятности с порогом.
- Всегда масштабируйте признаки с помощью
StandardScalerперед обучением — это ускоряет сходимость и улучшает точность. - Используйте Pipeline для объединения масштабирования и модели; это предотвращает утечку данных и упрощает развёртывание.
- Оценивайте с помощью точности, полноты, F1 и AUC-ROC, а не только по точности, особенно при несбалансированных данных. Смотрите главы Матрица ошибок и Кривая AUC-ROC.
- Управляйте переобучением с помощью параметра
C(меньше = сильнее регуляризация); используйте кросс-валидацию или grid search для настройки. - Для многоклассовых задач используйте
solver="lbfgs"(по умолчанию); scikit-learn 1.5+ всегда использует softmax (многочленный), который хорошо справляется с перекрывающимися классами.