Skip to content

Логистическая регрессия (Logistic Regression)

Логистическая регрессия — это статистический метод классификации, используемый для предсказания вероятности принадлежности объекта к определённому классу. Несмотря на название «регрессия», алгоритм применяется для решения задач бинарной и многоклассовой классификации.

Подробное описание

Постановка задачи: Даны обучающие данные с признаками \(X\) и соответствующими им метками классов \(y\). Необходимо построить модель, которая по новым данным \(X_{new}\) предсказывает вероятность принадлежности к каждому из классов.

Входные данные:

  • Матрица признаков \(X\) размером \(n \times m\), где \(n\) — количество объектов, \(m\) — количество признаков.
  • Вектор целевых переменных \(y\) длиной \(n\), содержащий метки классов (например, 0 или 1 для бинарной классификации).

Выходные данные:

  • Вероятность принадлежности объекта к каждому классу.
  • Предсказанный класс (на основе порога вероятности, обычно 0.5).

Ключевая идея: Вместо прямого предсказания класса, логистическая регрессия моделирует вероятность того, что объект принадлежит к определённому классу, используя сигмоидную функцию (для бинарной классификации) или softmax-функцию (для многоклассовой). Эта вероятность затем преобразуется в конкретный класс.

Исторический контекст: Логистическая регрессия была разработана в 1958 году Дэвидом Коксом и стала одним из первых методов машинного обучения, широко применяемых в медицине, экономике и социальных науках.

Основные принципы

Математическая формулировка

Для бинарной классификации используется сигмоидная функция:

\[ P(y=1|X) = \sigma(z) = \frac{1}{1 + e^{-z}} \]

Где:

  • \(z = w^T X + b\) — линейная комбинация признаков
  • \(w\) — вектор весов модели
  • \(b\) — смещение (bias)
  • \(\sigma(z)\) — сигмоидная функция, отображающая любое вещественное число в интервал \((0, 1)\)

Функция потерь (логарифмическая правдоподобие):

\[ L(w, b) = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\hat{y}_i) + (1-y_i) \log(1-\hat{y}_i)] \]

Где:

  • \(\hat{y}_i = \sigma(w^T x_i + b)\) — предсказанная вероятность для i-го объекта
  • \(y_i\) — истинная метка класса для i-го объекта

Оптимизация выполняется методом градиентного спуска или его вариантами (SGD, Adam и др.).

flowchart TD
    A[Входные данные X, y] --> B[Инициализация весов w и смещения b]
    B --> C[Вычисление z = w^T * X + b]
    C --> D[Применение сигмоидной функции P = 1 / (1 + exp(-z))]
    D --> E[Расчёт функции потерь L]
    E --> F{Сходимость?}
    F -->|Нет| G[Обновление весов через градиентный спуск]
    G --> C
    F -->|Да| H[Возврат обученной модели]

Пример реализации на Python

import numpy as np
from typing import Tuple


class LogisticRegression:
    """Реализация логистической регрессии для бинарной классификации."""

    def __init__(self, learning_rate: float = 0.01, n_iterations: int = 1000):
        """
        Инициализация модели.

        Args:
            learning_rate: Скорость обучения
            n_iterations: Количество итераций градиентного спуска
        """
        self.learning_rate = learning_rate
        self.n_iterations = n_iterations
        self.weights = None
        self.bias = None

    def _sigmoid(self, z: np.ndarray) -> np.ndarray:
        """
        Сигмоидная функция.

        Args:
            z: Линейная комбинация признаков

        Returns:
            Вероятности в диапазоне (0, 1)
        """
        # Ограничиваем значения для предотвращения overflow
        z = np.clip(z, -500, 500)
        return 1 / (1 + np.exp(-z))

    def fit(self, X: np.ndarray, y: np.ndarray) -> None:
        """
        Обучение модели.

        Args:
            X: Матрица признаков размером (n_samples, n_features)
            y: Вектор меток классов размером (n_samples,)
        """
        n_samples, n_features = X.shape

        # Инициализация весов нулями
        self.weights = np.zeros(n_features)
        self.bias = 0

        # Градиентный спуск
        for _ in range(self.n_iterations):
            # Вычисление линейной комбинации
            linear_model = np.dot(X, self.weights) + self.bias

            # Применение сигмоидной функции
            predictions = self._sigmoid(linear_model)

            # Вычисление градиентов
            dw = (1 / n_samples) * np.dot(X.T, (predictions - y))
            db = (1 / n_samples) * np.sum(predictions - y)

            # Обновление весов
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db

    def predict_proba(self, X: np.ndarray) -> np.ndarray:
        """
        Предсказание вероятностей.

        Args:
            X: Матрица признаков

        Returns:
            Вероятности принадлежности к классу 1
        """
        linear_model = np.dot(X, self.weights) + self.bias
        return self._sigmoid(linear_model)

    def predict(self, X: np.ndarray, threshold: float = 0.5) -> np.ndarray:
        """
        Предсказание классов.

        Args:
            X: Матрица признаков
            threshold: Порог для классификации

        Returns:
            Предсказанные метки классов (0 или 1)
        """
        probabilities = self.predict_proba(X)
        return (probabilities >= threshold).astype(int)


if __name__ == "__main__":
    # Пример использования
    from sklearn.datasets import make_classification
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score

    # Генерация синтетических данных
    X, y = make_classification(
        n_samples=1000,
        n_features=20,
        n_informative=15,
        n_redundant=5,
        random_state=42
    )

    # Разделение на обучающую и тестовую выборки
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )

    # Создание и обучение модели
    model = LogisticRegression(learning_rate=0.1, n_iterations=1000)
    model.fit(X_train, y_train)

    # Предсказания
    y_pred = model.predict(X_test)

    # Оценка точности
    accuracy = accuracy_score(y_test, y_pred)
    print(f"Точность модели: {accuracy:.4f}")

    # Пример предсказания для одного объекта
    sample = X_test[0].reshape(1, -1)
    probability = model.predict_proba(sample)[0]
    predicted_class = model.predict(sample)[0]
    print(f"Вероятность класса 1: {probability:.4f}")
    print(f"Предсказанный класс: {predicted_class}")

Достоинства и недостатки

Достоинства:

  1. Интерпретируемость — веса модели показывают важность каждого признака, что позволяет понять влияние факторов на результат.
  2. Вычислительная эффективность — алгоритм быстро обучается даже на больших наборах данных благодаря простой математической структуре.
  3. Хорошая работа с линейно разделимыми данными — показывает отличные результаты, когда классы можно разделить гиперплоскостью.
  4. Вероятностный вывод — предоставляет не только класс, но и уверенность предсказания в виде вероятности.

Недостатки:

  1. Линейность границы решений — не может эффективно работать с данными, требующими нелинейной границы разделения без предварительного создания полиномиальных признаков.
  2. Чувствительность к выбросам — экстремальные значения могут значительно искажать веса модели.
  3. Предположение о независимости признаков — сильно коррелированные признаки могут ухудшить качество модели.
  4. Проблемы с несбалансированными данными — требует дополнительных техник (взвешивание классов, oversampling) при сильном дисбалансе классов.

Области применения

  1. Медицина (диагностика заболеваний на основе симптомов и анализов, прогнозирование риска развития болезней)
  2. Экономика и финансы (оценка кредитоспособности клиентов, обнаружение мошеннических транзакций, прогнозирование дефолтов)
  3. Маркетинг (прогнозирование оттока клиентов, определение вероятности покупки товара, таргетированная реклама)
  4. Обработка текста (классификация спама, анализ тональности отзывов, категоризация документов)