Home » Логистическая регрессия с Scikit Learn — учебник и примеры
Логистическая регрессия с Scikit Learn — учебник и примеры

Логистическая регрессия с Scikit Learn — учебник и примеры

Привет! Сегодня поговорим о логистической регрессии — одном из самых популярных алгоритмов машинного обучения для задач классификации. Несмотря на название, это не про регрессию в традиционном понимании, а мощный инструмент для предсказания бинарных и категориальных исходов. Если ты работаешь с серверами, то наверняка сталкивался с задачами мониторинга, анализа логов или автоматического принятия решений — логистическая регрессия может стать твоим верным помощником для классификации событий, предсказания сбоев или определения аномалий в работе инфраструктуры.

Scikit-learn делает работу с логистической регрессией максимально простой — буквально несколько строк кода, и у тебя готовая модель. Но как всегда, дьявол в деталях: правильная настройка параметров, подготовка данных и интерпретация результатов могут существенно повлиять на качество работы алгоритма. Разберём всё по полочкам с практическими примерами.

Как работает логистическая регрессия

Логистическая регрессия использует логистическую функцию (сигмоид) для преобразования линейной комбинации признаков в вероятность от 0 до 1. В отличие от линейной регрессии, которая предсказывает непрерывные значения, логистическая регрессия отлично подходит для задач классификации.

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

  • Сигмоидная функция: σ(z) = 1 / (1 + e^(-z)), где z = β₀ + β₁x₁ + β₂x₂ + … + βₙxₙ
  • Порог решения: обычно 0.5 — если вероятность выше, класс 1, иначе класс 0
  • Максимум правдоподобия: метод оптимизации для нахождения лучших коэффициентов
  • Линейная разделимость: алгоритм ищет линейную границу между классами

Быстрая настройка: от установки до первой модели

Начнём с базовой настройки окружения. Если у тебя есть VPS или выделенный сервер, можешь развернуть там Jupyter или просто работать через SSH.

# Установка необходимых пакетов
pip install scikit-learn pandas numpy matplotlib seaborn

# Или через conda
conda install scikit-learn pandas numpy matplotlib seaborn

Простейший пример классификации:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report

# Создаём простой датасет
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0, 
                          n_informative=2, 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(random_state=42)
model.fit(X_train, y_train)

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

print(f"Точность: {accuracy_score(y_test, y_pred):.3f}")
print(f"Вероятности первых 5 предсказаний:\n{y_pred_proba[:5]}")

Практические примеры и кейсы

Кейс 1: Анализ логов веб-сервера

Представим, что нужно классифицировать HTTP-запросы на нормальные и подозрительные:

# Пример данных из логов
import pandas as pd

# Симулируем данные логов
log_data = pd.DataFrame({
    'response_time': np.random.lognormal(0, 1, 1000),
    'request_size': np.random.exponential(1000, 1000),
    'status_code': np.random.choice([200, 404, 500], 1000, p=[0.8, 0.15, 0.05]),
    'user_agent_length': np.random.normal(50, 20, 1000),
    'is_suspicious': np.random.choice([0, 1], 1000, p=[0.9, 0.1])
})

# Подготовка признаков
features = ['response_time', 'request_size', 'user_agent_length']
X = log_data[features]
y = log_data['is_suspicious']

# Стандартизация данных
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Обучение модели
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.3, random_state=42)
model = LogisticRegression(random_state=42)
model.fit(X_train, y_train)

# Оценка качества
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))

# Важность признаков
feature_importance = pd.DataFrame({
    'feature': features,
    'coefficient': model.coef_[0],
    'abs_coefficient': np.abs(model.coef_[0])
}).sort_values('abs_coefficient', ascending=False)

print("\nВажность признаков:")
print(feature_importance)

Кейс 2: Предсказание нагрузки на сервер

# Классификация нагрузки: высокая/низкая
server_data = pd.DataFrame({
    'cpu_usage': np.random.beta(2, 5, 1000) * 100,
    'memory_usage': np.random.gamma(2, 10, 1000),
    'disk_io': np.random.exponential(50, 1000),
    'network_requests': np.random.poisson(100, 1000),
    'time_of_day': np.random.uniform(0, 24, 1000)
})

# Создаём целевую переменную: высокая нагрузка
server_data['high_load'] = ((server_data['cpu_usage'] > 70) | 
                           (server_data['memory_usage'] > 80)).astype(int)

# Циклические признаки для времени
server_data['hour_sin'] = np.sin(2 * np.pi * server_data['time_of_day'] / 24)
server_data['hour_cos'] = np.cos(2 * np.pi * server_data['time_of_day'] / 24)

features = ['cpu_usage', 'memory_usage', 'disk_io', 'network_requests', 'hour_sin', 'hour_cos']
X = server_data[features]
y = server_data['high_load']

# Модель с регуляризацией
model_l1 = LogisticRegression(penalty='l1', solver='liblinear', random_state=42)
model_l2 = LogisticRegression(penalty='l2', solver='lbfgs', random_state=42)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model_l1.fit(X_train, y_train)
model_l2.fit(X_train, y_train)

print(f"L1 регуляризация - точность: {model_l1.score(X_test, y_test):.3f}")
print(f"L2 регуляризация - точность: {model_l2.score(X_test, y_test):.3f}")

Сравнение параметров и их влияние

Параметр Значение по умолчанию Влияние Рекомендации
penalty ‘l2’ Тип регуляризации L1 для отбора признаков, L2 для стабильности
C 1.0 Сила регуляризации Меньше значение = сильнее регуляризация
solver ‘lbfgs’ Алгоритм оптимизации lbfgs для небольших данных, sag/saga для больших
max_iter 100 Количество итераций Увеличить при предупреждениях о сходимости
class_weight None Баланс классов ‘balanced’ для несбалансированных данных

Продвинутые техники и настройки

Настройка гиперпараметров

from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# Создаём пайплайн
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', LogisticRegression(random_state=42))
])

# Параметры для поиска
param_grid = {
    'classifier__C': [0.01, 0.1, 1, 10, 100],
    'classifier__penalty': ['l1', 'l2', 'elasticnet'],
    'classifier__solver': ['lbfgs', 'liblinear', 'saga'],
    'classifier__max_iter': [100, 200, 500]
}

# Исправляем несовместимые комбинации
param_grid = [
    {
        'classifier__C': [0.01, 0.1, 1, 10, 100],
        'classifier__penalty': ['l1', 'l2'],
        'classifier__solver': ['liblinear'],
        'classifier__max_iter': [100, 200, 500]
    },
    {
        'classifier__C': [0.01, 0.1, 1, 10, 100],
        'classifier__penalty': ['l2'],
        'classifier__solver': ['lbfgs'],
        'classifier__max_iter': [100, 200, 500]
    }
]

# Grid search
grid_search = GridSearchCV(pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
grid_search.fit(X_train, y_train)

print(f"Лучшие параметры: {grid_search.best_params_}")
print(f"Лучший результат: {grid_search.best_score_:.3f}")

Работа с несбалансированными данными

# Создаём несбалансированный датасет
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from collections import Counter

X_imb, y_imb = make_classification(n_samples=1000, n_features=10, 
                                  n_informative=5, n_redundant=5,
                                  weights=[0.9, 0.1], random_state=42)

print(f"Исходное распределение: {Counter(y_imb)}")

# Методы борьбы с несбалансированностью
methods = {
    'baseline': LogisticRegression(random_state=42),
    'class_weight': LogisticRegression(class_weight='balanced', random_state=42),
    'smote': LogisticRegression(random_state=42)
}

X_train, X_test, y_train, y_test = train_test_split(X_imb, y_imb, test_size=0.2, random_state=42)

results = {}
for name, model in methods.items():
    if name == 'smote':
        # Применяем SMOTE
        smote = SMOTE(random_state=42)
        X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
        model.fit(X_train_smote, y_train_smote)
    else:
        model.fit(X_train, y_train)
    
    y_pred = model.predict(X_test)
    results[name] = accuracy_score(y_test, y_pred)

print("\nСравнение методов:")
for method, accuracy in results.items():
    print(f"{method}: {accuracy:.3f}")

Интеграция с другими инструментами

Сохранение и загрузка модели

import joblib
import pickle

# Сохранение модели
joblib.dump(model, 'logistic_model.pkl')

# Или с помощью pickle
with open('model.pickle', 'wb') as f:
    pickle.dump(model, f)

# Загрузка модели
loaded_model = joblib.load('logistic_model.pkl')

# Проверка
print(f"Модель загружена: {loaded_model.score(X_test, y_test):.3f}")

Веб-API для предсказаний

# Flask API для модели
from flask import Flask, request, jsonify
import numpy as np

app = Flask(__name__)

# Загружаем модель при старте
model = joblib.load('logistic_model.pkl')

@app.route('/predict', methods=['POST'])
def predict():
    try:
        data = request.json
        features = np.array(data['features']).reshape(1, -1)
        
        prediction = model.predict(features)[0]
        probability = model.predict_proba(features)[0]
        
        return jsonify({
            'prediction': int(prediction),
            'probability': probability.tolist(),
            'status': 'success'
        })
    except Exception as e:
        return jsonify({'error': str(e), 'status': 'error'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Мониторинг и логирование

import logging
from datetime import datetime

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('model_predictions.log'),
        logging.StreamHandler()
    ]
)

class LogisticRegressionMonitor:
    def __init__(self, model, threshold=0.5):
        self.model = model
        self.threshold = threshold
        self.predictions_count = 0
        self.accuracy_history = []
    
    def predict_with_logging(self, X, y_true=None):
        start_time = datetime.now()
        
        predictions = self.model.predict(X)
        probabilities = self.model.predict_proba(X)
        
        prediction_time = (datetime.now() - start_time).total_seconds()
        self.predictions_count += len(X)
        
        # Логируем основную информацию
        logging.info(f"Predictions: {len(X)}, Time: {prediction_time:.3f}s")
        
        # Если есть истинные значения, считаем точность
        if y_true is not None:
            accuracy = accuracy_score(y_true, predictions)
            self.accuracy_history.append(accuracy)
            logging.info(f"Accuracy: {accuracy:.3f}")
            
            # Проверяем на дрейф качества
            if len(self.accuracy_history) > 10:
                recent_accuracy = np.mean(self.accuracy_history[-10:])
                if recent_accuracy < 0.7:  # Пороговое значение
                    logging.warning(f"Model quality degradation detected: {recent_accuracy:.3f}")
        
        return predictions, probabilities
    
    def get_stats(self):
        return {
            'total_predictions': self.predictions_count,
            'average_accuracy': np.mean(self.accuracy_history) if self.accuracy_history else None,
            'recent_accuracy': np.mean(self.accuracy_history[-10:]) if len(self.accuracy_history) >= 10 else None
        }

# Использование
monitor = LogisticRegressionMonitor(model)
predictions, probabilities = monitor.predict_with_logging(X_test, y_test)
print(monitor.get_stats())

Альтернативные решения и сравнение

Алгоритм Преимущества Недостатки Когда использовать
Логистическая регрессия Быстрая, интерпретируемая, хорошо работает с линейно разделимыми данными Плохо работает с нелинейными зависимостями Базовый алгоритм, интерпретируемость важна
Random Forest Работает с нелинейными данными, устойчив к выбросам Менее интерпретируем, может переобучаться Сложные нелинейные зависимости
SVM Эффективен в высокомерных пространствах Медленный на больших данных Высокомерные данные, небольшие датасеты
Gradient Boosting Высокое качество предсказаний Долгое обучение, сложная настройка Максимальное качество предсказаний

Интересные факты и нестандартные применения

  • Онлайн обучение: Scikit-learn поддерживает инкрементальное обучение через SGDClassifier с loss=’log’
  • Мультиклассовая классификация: Логистическая регрессия автоматически использует One-vs-Rest или multinomial подходы
  • Калибровка вероятностей: Можно использовать CalibratedClassifierCV для более точных вероятностей
  • Интерпретация коэффициентов: exp(коэффициент) показывает во сколько раз увеличивается шанс при изменении признака на единицу

Онлайн обучение для стриминговых данных

from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
import numpy as np

# Модель для онлайн обучения
online_model = SGDClassifier(loss='log', random_state=42)
scaler = StandardScaler()

# Симуляция потока данных
batch_size = 100
n_batches = 10

for batch_num in range(n_batches):
    # Генерируем новый батч данных
    X_batch, y_batch = make_classification(n_samples=batch_size, n_features=10, 
                                          n_informative=5, random_state=batch_num)
    
    # Масштабируем данные
    if batch_num == 0:
        X_batch_scaled = scaler.fit_transform(X_batch)
        online_model.fit(X_batch_scaled, y_batch)
    else:
        X_batch_scaled = scaler.transform(X_batch)
        online_model.partial_fit(X_batch_scaled, y_batch)
    
    # Логируем прогресс
    if batch_num % 2 == 0:
        score = online_model.score(X_batch_scaled, y_batch)
        print(f"Batch {batch_num}: accuracy = {score:.3f}")

Автоматизация и скрипты

Скрипт для автоматического мониторинга

#!/usr/bin/env python3
"""
Скрипт для автоматического мониторинга и переобучения модели
"""
import argparse
import json
import pandas as pd
from datetime import datetime, timedelta
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
import joblib
import os

class ModelManager:
    def __init__(self, model_path='model.pkl', config_path='config.json'):
        self.model_path = model_path
        self.config_path = config_path
        self.load_config()
        
    def load_config(self):
        """Загружает конфигурацию"""
        with open(self.config_path, 'r') as f:
            self.config = json.load(f)
    
    def load_model(self):
        """Загружает модель"""
        if os.path.exists(self.model_path):
            return joblib.load(self.model_path)
        return None
    
    def save_model(self, model):
        """Сохраняет модель"""
        joblib.dump(model, self.model_path)
        print(f"Model saved to {self.model_path}")
    
    def retrain_model(self, data_path):
        """Переобучает модель"""
        print("Starting model retraining...")
        
        # Загружаем данные
        data = pd.read_csv(data_path)
        
        # Подготавливаем данные
        X = data[self.config['features']]
        y = data[self.config['target']]
        
        # Разделяем данные
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=0.2, random_state=42
        )
        
        # Обучаем модель
        model = LogisticRegression(**self.config['model_params'])
        model.fit(X_train, y_train)
        
        # Оцениваем качество
        accuracy = model.score(X_test, y_test)
        print(f"New model accuracy: {accuracy:.3f}")
        
        # Сохраняем если качество приемлемо
        if accuracy >= self.config['min_accuracy']:
            self.save_model(model)
            return True
        else:
            print(f"Model quality too low: {accuracy:.3f} < {self.config['min_accuracy']}")
            return False

def main():
    parser = argparse.ArgumentParser(description='Model management script')
    parser.add_argument('--retrain', action='store_true', help='Retrain model')
    parser.add_argument('--data', type=str, help='Path to training data')
    parser.add_argument('--config', type=str, default='config.json', help='Config file path')
    
    args = parser.parse_args()
    
    manager = ModelManager(config_path=args.config)
    
    if args.retrain:
        if not args.data:
            print("Error: --data required for retraining")
            return
        
        success = manager.retrain_model(args.data)
        if success:
            print("Model retrained successfully")
        else:
            print("Model retraining failed")

if __name__ == '__main__':
    main()

Пример конфигурационного файла

# config.json
{
    "features": ["feature1", "feature2", "feature3"],
    "target": "target_column",
    "model_params": {
        "C": 1.0,
        "penalty": "l2",
        "solver": "lbfgs",
        "max_iter": 200,
        "random_state": 42
    },
    "min_accuracy": 0.75,
    "retraining_schedule": "daily"
}

Производительность и оптимизация

Бенчмарк разных солверов

import time
from sklearn.datasets import make_classification

# Создаём данные разного размера
sizes = [1000, 5000, 10000, 50000]
solvers = ['lbfgs', 'liblinear', 'sag', 'saga']

results = []

for size in sizes:
    print(f"\nTesting with {size} samples:")
    X, y = make_classification(n_samples=size, n_features=20, random_state=42)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
    for solver in solvers:
        try:
            start_time = time.time()
            model = LogisticRegression(solver=solver, max_iter=1000, random_state=42)
            model.fit(X_train, y_train)
            training_time = time.time() - start_time
            
            accuracy = model.score(X_test, y_test)
            
            results.append({
                'size': size,
                'solver': solver,
                'training_time': training_time,
                'accuracy': accuracy
            })
            
            print(f"  {solver}: {training_time:.3f}s, accuracy: {accuracy:.3f}")
            
        except Exception as e:
            print(f"  {solver}: Failed - {e}")

# Анализ результатов
results_df = pd.DataFrame(results)
print("\nSummary by solver:")
print(results_df.groupby('solver').agg({
    'training_time': 'mean',
    'accuracy': 'mean'
}).round(3))

Заключение и рекомендации

Логистическая регрессия остаётся одним из самых полезных алгоритмов в арсенале каждого разработчика. Особенно для серверных задач — она быстрая, предсказуемая и хорошо интерпретируемая. Вот мои главные рекомендации:

  • Начинай с простого: Логистическая регрессия — отличный базовый алгоритм для большинства задач классификации
  • Масштабируй данные: Особенно важно для алгоритмов, основанных на градиентном спуске
  • Используй регуляризацию: L1 для отбора признаков, L2 для стабильности
  • Мониторь качество: Настрой автоматическое логирование и отслеживание метрик
  • Автоматизируй процессы: Создай скрипты для переобучения и валидации моделей

Для продакшена рекомендую:

  • Использовать VPS или выделенный сервер для стабильного развёртывания
  • Настроить мониторинг качества модели в реальном времени
  • Реализовать A/B тестирование для сравнения разных версий модели
  • Создать пайплайн для автоматического переобучения

Логистическая регрессия не решит все твои проблемы, но станет надёжным фундаментом для построения более сложных ML-систем. Главное — не забывай про качество данных и правильную валидацию. Удачи в экспериментах!


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked