- Home »

Логистическая регрессия с 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-систем. Главное — не забывай про качество данных и правильную валидацию. Удачи в экспериментах!
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.