← Назад к вопросам
Python: Реализовать логистическую регрессию с нуля
1.8 Middle🔥 151 комментариев
#Python#Машинное обучение
Условие
Реализуйте алгоритм логистической регрессии с нуля на Python.
Требования:
- Sigmoid функция
- Binary cross-entropy loss
- Градиентный спуск
- Тестирование на бинарной классификации
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
1. Sigmoid функция и Binary Cross-Entropy Loss
import numpy as np
import matplotlib.pyplot as plt
class LogisticRegression:
"""Логистическая регрессия для бинарной классификации
P(y=1|x) = sigmoid(w^T x + b) = 1 / (1 + e^(-w^T x - b))
Loss = -1/n * sum(y*log(p) + (1-y)*log(1-p))
"""
def __init__(self, learning_rate=0.01, n_iterations=1000, verbose=False):
self.learning_rate = learning_rate
self.n_iterations = n_iterations
self.verbose = verbose
self.w = None
self.b = None
self.loss_history = []
@staticmethod
def sigmoid(z):
"""Sigmoid функция: 1 / (1 + e^(-z))
Преобразует любое число в вероятность [0, 1]
"""
return 1 / (1 + np.exp(-z))
@staticmethod
def binary_cross_entropy(y_true, y_pred):
"""Binary Cross-Entropy Loss
L = -1/n * sum(y*log(p) + (1-y)*log(1-p))
y_true: истинные метки (0 или 1)
y_pred: предсказанные вероятности [0, 1]
"""
n = len(y_true)
# Избегаем log(0)
y_pred = np.clip(y_pred, 1e-15, 1 - 1e-15)
loss = -1/n * np.sum(y_true * np.log(y_pred) +
(1 - y_true) * np.log(1 - y_pred))
return loss
def fit(self, X, y):
"""Обучение модели через градиентный спуск
Args:
X: (n_samples, n_features)
y: (n_samples,) с метками 0 или 1
"""
n_samples, n_features = X.shape
# Инициализация весов
self.w = np.zeros(n_features)
self.b = 0
# Градиентный спуск
for iteration in range(self.n_iterations):
# Forward pass: вычисляем z = X @ w + b
z = X @ self.w + self.b
# Применяем sigmoid
y_pred = self.sigmoid(z)
# Вычисляем ошибку
error = y_pred - y
# Вычисляем градиенты
# dL/dw = 1/n * X^T @ error
# dL/db = 1/n * sum(error)
dw = (1 / n_samples) * (X.T @ error)
db = (1 / n_samples) * np.sum(error)
# Обновляем веса
self.w -= self.learning_rate * dw
self.b -= self.learning_rate * db
# Вычисляем loss
loss = self.binary_cross_entropy(y, y_pred)
self.loss_history.append(loss)
if self.verbose and (iteration + 1) % 100 == 0:
print(f"Iteration {iteration + 1}/{self.n_iterations}, Loss: {loss:.6f}")
return self
def predict_proba(self, X):
"""Предсказание вероятности класса 1"""
z = X @ self.w + self.b
return self.sigmoid(z)
def predict(self, X, threshold=0.5):
"""Предсказание класса (0 или 1)
По умолчанию используем порог 0.5
"""
proba = self.predict_proba(X)
return (proba >= threshold).astype(int)
print("=== ЛОГИСТИЧЕСКАЯ РЕГРЕССИЯ ===")
print("\n=== SIGMOID ФУНКЦИЯ ===")
# Визуализация sigmoid
z = np.linspace(-6, 6, 100)
sigmoid_vals = 1 / (1 + np.exp(-z))
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(z, sigmoid_vals, linewidth=2, color='steelblue')
axes[0].axhline(y=0.5, color='red', linestyle='--', alpha=0.5)
axes[0].axvline(x=0, color='gray', linestyle='--', alpha=0.3)
axes[0].set_xlabel('z')
axes[0].set_ylabel('sigmoid(z)')
axes[0].set_title('Sigmoid Function')
axes[0].grid(True, alpha=0.3)
axes[0].set_ylim(-0.1, 1.1)
print("\nSigmoid свойства:")
print(f" sigmoid(0) = {LogisticRegression.sigmoid(np.array([0]))[0]:.4f}")
print(f" sigmoid(-∞) ≈ {LogisticRegression.sigmoid(np.array([-10]))[0]:.4f}")
print(f" sigmoid(+∞) ≈ {LogisticRegression.sigmoid(np.array([10]))[0]:.4f}")
print(f" Диапазон: [0, 1] (вероятность)")
# === СИНТЕТИЧЕСКИЕ ДАННЫЕ ===
np.random.seed(42)
n_samples = 200
# Класс 0
X0 = np.random.randn(n_samples // 2, 2) - 2
y0 = np.zeros(n_samples // 2)
# Класс 1
X1 = np.random.randn(n_samples // 2, 2) + 2
y1 = np.ones(n_samples // 2)
X = np.vstack([X0, X1])
y = np.hstack([y0, y1])
print(f"\nДанные для классификации:")
print(f" Размер: {X.shape}")
print(f" Класс 0: {np.sum(y == 0)} примеров")
print(f" Класс 1: {np.sum(y == 1)} примеров")
# Визуализация
axes[1].scatter(X0[:, 0], X0[:, 1], c='blue', label='Class 0', alpha=0.6)
axes[1].scatter(X1[:, 0], X1[:, 1], c='red', label='Class 1', alpha=0.6)
axes[1].set_xlabel('Feature 1')
axes[1].set_ylabel('Feature 2')
axes[1].set_title('Binary Classification Dataset')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('logistic_regression_intro.png')
print("\nДатасет визуализирован")
2. Обучение модели
print("\n=== ОБУЧЕНИЕ МОДЕЛИ ===")
model = LogisticRegression(
learning_rate=0.1,
n_iterations=1000,
verbose=True
)
model.fit(X, y)
print(f"\nОбученные параметры:")
print(f" Веса: {model.w}")
print(f" Смещение: {model.b:.6f}")
# === ПРЕДСКАЗАНИЕ ===
y_pred = model.predict(X)
y_proba = model.predict_proba(X)
print(f"\nПредсказания на обучающем наборе:")
print(f" Форма: {y_pred.shape}")
print(f" Классы: {np.unique(y_pred)}")
print(f" Распределение: {np.bincount(y_pred)}")
3. Метрики оценки
print("\n=== МЕТРИКИ ОЦЕНКИ ===")
def accuracy(y_true, y_pred):
return np.mean(y_true == y_pred)
def precision(y_true, y_pred):
tp = np.sum((y_pred == 1) & (y_true == 1))
fp = np.sum((y_pred == 1) & (y_true == 0))
return tp / (tp + fp) if (tp + fp) > 0 else 0
def recall(y_true, y_pred):
tp = np.sum((y_pred == 1) & (y_true == 1))
fn = np.sum((y_pred == 0) & (y_true == 1))
return tp / (tp + fn) if (tp + fn) > 0 else 0
def f1_score(y_true, y_pred):
p = precision(y_true, y_pred)
r = recall(y_true, y_pred)
return 2 * p * r / (p + r) if (p + r) > 0 else 0
acc = accuracy(y, y_pred)
prec = precision(y, y_pred)
rec = recall(y, y_pred)
f1 = f1_score(y, y_pred)
print(f" Accuracy: {acc:.4f}")
print(f" Precision: {prec:.4f}")
print(f" Recall: {rec:.4f}")
print(f" F1-Score: {f1:.4f}")
# Confusion Matrix
tp = np.sum((y_pred == 1) & (y == 1))
fp = np.sum((y_pred == 1) & (y == 0))
fn = np.sum((y_pred == 0) & (y == 1))
tn = np.sum((y_pred == 0) & (y == 0))
print(f"\nConfusion Matrix:")
print(f" TN={tn}, FP={fp}")
print(f" FN={fn}, TP={tp}")
4. Визуализация результатов
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Loss график
axes[0, 0].plot(model.loss_history, linewidth=2, color='steelblue')
axes[0, 0].set_xlabel('Iteration')
axes[0, 0].set_ylabel('Loss (Binary Cross-Entropy)')
axes[0, 0].set_title('Training Loss Convergence')
axes[0, 0].grid(True, alpha=0.3)
# Decision Boundary
xx, yy = np.meshgrid(np.linspace(X[:, 0].min() - 1, X[:, 0].max() + 1, 100),
np.linspace(X[:, 1].min() - 1, X[:, 1].max() + 1, 100))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
axes[0, 1].contourf(xx, yy, Z, alpha=0.3, levels=[0, 0.5, 1], colors=['blue', 'red'])
axes[0, 1].scatter(X0[:, 0], X0[:, 1], c='blue', label='Class 0', alpha=0.6)
axes[0, 1].scatter(X1[:, 0], X1[:, 1], c='red', label='Class 1', alpha=0.6)
axes[0, 1].set_title('Decision Boundary')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
# Probability Distribution
axes[1, 0].hist(y_proba[y == 0], bins=20, alpha=0.6, label='Class 0', color='blue')
axes[1, 0].hist(y_proba[y == 1], bins=20, alpha=0.6, label='Class 1', color='red')
axes[1, 0].axvline(x=0.5, color='black', linestyle='--', linewidth=2)
axes[1, 0].set_xlabel('Predicted Probability')
axes[1, 0].set_ylabel('Count')
axes[1, 0].set_title('Probability Distribution by Class')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3, axis='y')
# Метрики
metrics = ['Accuracy', 'Precision', 'Recall', 'F1']
values = [acc, prec, rec, f1]
colors = ['steelblue', 'orange', 'green', 'red']
axes[1, 1].bar(metrics, values, color=colors, alpha=0.7)
axes[1, 1].set_ylim([0, 1])
axes[1, 1].set_ylabel('Score')
axes[1, 1].set_title('Classification Metrics')
for i, v in enumerate(values):
axes[1, 1].text(i, v + 0.02, f'{v:.3f}', ha='center')
axes[1, 1].grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('logistic_regression_results.png')
print(f"\nРезультаты визуализированы")
print(f"""
=== ИТОГИ ===
Логистическая регрессия:
✓ Реализована sigmoid функция
✓ Реализована binary cross-entropy loss
✓ Реализован градиентный спуск
✓ Получена высокая точность ({acc:.2%})
✓ Визуализированы результаты
Для многоклассовой классификации:
- Используй softmax вместо sigmoid
- Используй categorical cross-entropy loss
- One-vs-Rest или One-vs-One стратегии
""")