Фундаментальные вопросы по ML/DL, часть 1: Вопрос → Краткий ответ → Разбор → Пример кода. Линейки. Байес. Регуляризация

от автора

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

Времени мало, объема много, цели амбициозные — нужно научиться легко и быстро объяснять, но так же не лишая полноты!

Обращу внимание, самый действенный способ разобраться и запомнить — это своими руками поисследовать задачу! Это самое важное, оно происходит в секции с кодом.

Будет здорово получить ваши задачи и в следующих выпусках разобрать!

Я считаю самый полный и простой способ заполнить все пробелы — это взять хороший экзамен и ответить на все вопросы — понятно и быстро. А что запомнилось решить задачку. Приступим!

Сначала попробуйте сами быстро ответить, а потом после просмотра! Стало быстрее-понятнее объяснять?

Для более полного погружения в конце приложу важные ресурсы. Делитесь своими!

📚 Глава 1: Модели, метрики и формула Байеса

0. Задача обучения с учителем. Регрессия, Классификация

📌 Краткий ответ
  • Обучение с учителем — это постановка задачи, при которой каждый объект обучающей выборки снабжён целевым значением y, и модель обучается приближать отображение f(x) \approx y.

  • Регрессия: если y \in \mathbb{R} (например, цена, температура).

  • Классификация: если y \in \{1, \dots, K\}, то есть класс или категория (например, диагноз, категория изображения).

🔬 Подробный разбор

Общая постановка задачи

В обучении с учителем задана обучающая выборка из пар

(x_i, y_i), \quad i = 1, \dots, n,

где x_i \in \mathbb{R}^d — вектор признаков, y_i — целевая переменная. Требуется построить алгоритм f(x), минимизирующий ошибку предсказания.


Регрессия

Если y_i \in \mathbb{R} или \mathbb{R}^k, задача называется регрессией.
Модель должна предсказывать численное значение. Типичные функции потерь:

  • Mean Squared Error (MSE)

  • Mean Absolute Error (MAE)

Примеры:

  • Прогнозирование цены недвижимости

  • Оценка спроса на товар


Классификация

Если y_i \in \{1, \dots, K\} — задача классификации.
В простейшем случае — бинарная классификация (например, «да/нет»).
При

K > 2многоклассовая. Также существует multi-label классификация, когда одному объекту соответствуют несколько меток.

Модель выдает либо вероятности по классам (soft), либо сразу метку (hard). Часто оптимизируют logloss или используют surrogate-функции.

Примеры:

  • Распознавание рукописных цифр (0–9)

  • Классификация e-mail как «спам / не спам»

💻 Отрисовываем предсказания линейной и логистической регресии

Заглянем чуть дальше и покажем, пример решения задачи регресии (линейной регрессией) и классификации (логистической регрессией)

from sklearn.linear_model import LinearRegression, LogisticRegression from sklearn.datasets import make_regression, make_classification  # --- Регрессия --- X_reg, y_reg = make_regression(n_samples=100, n_features=2, noise=0.1, random_state=43) # [100, 2], [100] reg = LinearRegression().fit(X_reg, y_reg)  # --- Классификация --- X_clf, y_clf = make_classification(n_samples=100, n_features=2, n_classes=2, n_redundant=0, random_state=43) # [100, 2], [100] clf = LogisticRegression().fit(X_clf, y_clf) 
  # --- Отрисовка --- import matplotlib.pyplot as plt import numpy as np  # Создаем фигуру с двумя подграфиками fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))  # --- Регрессия --- # Создаем сетку точек для линии регрессии x_grid = np.linspace(X_reg[:, 0].min(), X_reg[:, 0].max(), 100).reshape(-1, 1) # Добавляем второй признак (среднее значение) # отрисовать только 1 признак можем => по второму усредним!  # так делать очень плохо! но для игрушечного примера - ок! x_grid_full = np.column_stack([x_grid, np.full_like(x_grid, X_reg[:, 1].mean())]) y_pred = reg.predict(x_grid_full)  # Визуализация регрессии ax1.scatter(X_reg[:, 0], y_reg, alpha=0.5, label='Данные') ax1.plot(x_grid, y_pred, 'r-', label='Линия регрессии') ax1.set_title('Регрессия') ax1.set_xlabel('Признак 1') ax1.set_ylabel('Целевая переменная') ax1.legend()  # --- Классификация --- # Создаем сетку точек для границы принятия решений x_min, x_max = X_clf[:, 0].min() - 0.5, X_clf[:, 0].max() + 0.5 y_min, y_max = X_clf[:, 1].min() - 0.5, X_clf[:, 1].max() + 0.5 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),                      np.arange(y_min, y_max, 0.02))  # Предсказываем классы для всех точек сетки Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape)  # Визуализация классификации ax2.contourf(xx, yy, Z, alpha=0.3, cmap='viridis') ax2.contour(xx, yy, Z, [0.5], colors='red', linewidths=2)  scatter = ax2.scatter(X_clf[:, 0], X_clf[:, 1], c=y_clf, cmap='viridis', alpha=0.5) ax2.set_title('Классификация') ax2.set_xlabel('Признак 1') ax2.set_ylabel('Признак 2') ax2.legend(*scatter.legend_elements(), title="Классы")  plt.tight_layout() plt.show() 

1. Метрики классификации: accuracy, balanced accuracy, precision, recall, f1-score, ROC-AUC, расширения для многоклассовой классификации

📌 Краткий ответ

Для задачи бинарной классификации (y \in {0, 1}) можно построить матрицу ошибок и по ним посчитать метрики:

Метрика

Формула

Смысл

Accuracy

Accuracy = (TP + TN) / (TP + TN + FP + FN)

Общая доля правильных предсказаний

Balanced Accuracy

0.5 * (TPR + TNR) = 0.5 * (TP / (TP + FN) + TN / (TN + FP))

Усреднённая точность по классам при дисбалансе

Precision

TP / (TP + FP)

Доля верных положительных предсказаний

Recall

TP / (TP + FN)

Доля найденных положительных среди всех реальных

F1(b)-score

2 * P * R / (P + R) = 2 / (1/P + 1/R) = [b=1] = (b^2 + 1) / (b^2 r^-1 + r^-1)

Баланс между precision и recall

AUC

Доля правильно упорядоченных пар среди (Negative, Positive)

Площадь под ROC-кривой (TPR (y) vs FPR (x) при разных порогах)

Легче запомнить, как TPR = recall позитивного класса, а FPR = 1 — recall негативного класса !

Как по мне самое простое и полезное переформулировка — это доля правильно упорядоченных пар среди (Negative, Positive)

  • Самый плохой случай — AUC=0.5 иначе можно реверснуть!

  • Лучшая метрика AUC=1


Для многоклассовой классификации — считаем для каждого класса one-vs-rest матрицу ошибок. Далее либо микро-усредняем (суммируем компоненты и считаем метрку) или макро-усреднение (по классам считаем и усредняем)

🔬 Подробный разбор

Очень подробно расписано здесь!

Обратите внимание так же на:

  • Recall@k, Precision@k

  • Average Precision

В следующих статьях будем отвечать на вопросы из практике — там и разгуляемся (иначе можно закапаться)!

💻 Визуализируем AUC ROC
 from sklearn.datasets import make_classification from sklearn.linear_model import LogisticRegression from sklearn.dummy import DummyClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import roc_curve, roc_auc_score import matplotlib.pyplot as plt  # --- 1. Синтетические, "грязные" данные --- X, y = make_classification(     n_samples=1000,     n_features=20,     n_informative=5,     n_redundant=4,     n_classes=2,     weights=[0.75, 0.25],  # дисбаланс классов     flip_y=0.1,            # 10% меток шумные     class_sep=0.8,         # классы частично пересекаются     random_state=42 )  # --- 2. Деление на train/test --- X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)  # --- 3. Логистическая регрессия --- model = LogisticRegression(max_iter=1000).fit(X_tr, y_tr) y_prob = model.predict_proba(X_te)[:, 1] fpr_model, tpr_model, _ = roc_curve(y_te, y_prob) auc_model = roc_auc_score(y_te, y_prob)  # --- 4. Dummy-классификатор (стратегия stratified) --- dummy = DummyClassifier(strategy='stratified', random_state=42).fit(X_tr, y_tr) y_dummy_prob = dummy.predict_proba(X_te)[:, 1] fpr_dummy, tpr_dummy, _ = roc_curve(y_te, y_dummy_prob) auc_dummy = roc_auc_score(y_te, y_dummy_prob)  # --- 5. Визуализация ROC-кривых --- plt.figure(figsize=(8, 6)) plt.plot(fpr_model, tpr_model, label=f"Logistic Regression (AUC = {auc_model:.2f})") plt.plot(fpr_dummy, tpr_dummy, linestyle='--', label=f"Dummy Stratified (AUC = {auc_dummy:.2f})") plt.plot([0, 1], [0, 1], 'k:', label="Random Guess (AUC = 0.50)")  plt.xlabel("False Positive Rate (FPR)") plt.ylabel("True Positive Rate (TPR)") plt.title("ROC-кривая: логистическая регрессия vs случайный классификатор") plt.legend() plt.grid(True) plt.tight_layout() plt.show() 

2. Метрики регрессии: MSE, MAE, R²

📌 Краткий ответ

Метрика

Формула

Смысл

MSE

mean((y_true - y_pred) ** 2)

Среднеквадратичная ошибка. Наказывает большие ошибки сильнее.

MAE

mean(abs(y_true - y_pred))

Средняя абсолютная ошибка. Интерпретируется в исходных единицах.

R² score

1 - MSE_model / MSE_const

На сколько лучше константного предсказания(=среднее при минимизации MSE) . От 0 до 1 (может быть < 0 при плохой модели).

🔬 Подробный разбор

MSE (Mean Squared Error)
Наиболее распространённая функция потерь. Ошибки возводятся в квадрат, что делает метрику чувствительной к выбросам.
MSE = mean((y - ŷ) ** 2)

MAE (Mean Absolute Error)
Абсолютное отклонение между предсказаниями и истиной. Менее чувствительна к выбросам(=робастнее), хорошо интерпретируется (в тех же единицах, что и целевая переменная).
MAE = mean(|y - ŷ|)

Huber Loss — гибрид между MSE и MAE: локально квадратичный штраф, дальше линейный.

R² (коэффициент детерминации)
Показывает, какую часть дисперсии целевой переменной объясняет модель.
R² = 1 - (MSE_model / MSE_const)
Где MSE_const — ошибка наивной модели, предсказывающей среднее.

💻 Сравниваем функции ошибок
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score from sklearn.datasets import make_regression from sklearn.linear_model import LinearRegression, HuberRegressor from sklearn.model_selection import train_test_split  # Данные X, y = make_regression(n_samples=500, noise=15, random_state=42) X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)  # --- Линейная регрессия --- model_lr = LinearRegression().fit(X_tr, y_tr) y_pred_lr = model_lr.predict(X_te)  print("=== Linear Regression ===") print("MSE:", mean_squared_error(y_te, y_pred_lr)) print("MAE:", mean_absolute_error(y_te, y_pred_lr)) print("R²:", r2_score(y_te, y_pred_lr))  # --- Huber-регрессия --- model_huber = HuberRegressor().fit(X_tr, y_tr) y_pred_huber = model_huber.predict(X_te)  print("\n=== Huber Regressor ===") print("MSE:", mean_squared_error(y_te, y_pred_huber)) print("MAE:", mean_absolute_error(y_te, y_pred_huber)) print("R²:", r2_score(y_te, y_pred_huber)) 
=== Linear Regression === MSE: 334.45719591398216 MAE: 14.30958669001259 R²: 0.988668164971938  === Huber Regressor === MSE: 367.2515287731075 MAE: 15.169297076822216 R²: 0.9875570512797974 

Эти метрики — они могут быть как лосс функциями, так и бизнесс метриками! Какой лосс минимизировать, нужно понять какая целевая бизнес метрика!

import numpy as np import matplotlib.pyplot as plt  # Ошибки (residuals) errors = np.linspace(-2, 2, 400)  # MSE: квадратичные потери mse_loss = errors ** 2  # MAE: абсолютные потери mae_loss = np.abs(errors)  # Huber loss delta = 1.0 huber_loss = np.where(     np.abs(errors) <= delta,     0.5 * errors ** 2,     delta * (np.abs(errors) - 0.5 * delta) )  # Визуализация plt.figure(figsize=(8, 6)) plt.plot(errors, mse_loss, label='MSE Loss', color='red') plt.plot(errors, mae_loss, label='MAE Loss', color='blue') plt.plot(errors, huber_loss, label='Huber Loss (δ = 1.0)', color='green') plt.xlabel("Ошибка (residual)") plt.ylabel("Значение функции потерь") plt.title("Сравнение MSE, MAE и Huber Loss") plt.legend() plt.grid(True) plt.tight_layout() plt.show() 

3. Оценка максимального правдоподобия (MLE), связь с регрессией и классификацией

📌 Краткий ответ

MLE (Maximum Likelihood Estimation) — метод оценки параметров, при котором максимизируется вероятность наблюдаемых данных.

Целевая переменная (и параметры модели) рассматриваются как слуайные величины.

Фиксируем класс моделей (например линейные) и ищем максимально правдоподобную модель среди них (=> нужно определить вероятность наблюдаемого семпла и при выводк воспользоваться независимостью семплов)!

  • В линейной регрессии (при нормальном шуме) MLE ⇔ минимизация MSE

  • В логистической регрессии MLE ⇔ минимизация логлосса

  • Регуляризация вносит априорные предположения (MAP) на веса. При нормальном, получаем L_2 регуляризацию, при лаплассе L_1.

🔬 Формальные выводы

Детальнее можно узнать в конце тетрадке.

✍ MLE и линейная регрессия

Предполагаем, что целевая переменная yᵢ имеет нормальное распределение с центром в xᵢᵀw и дисперсией σ²:

y_i \sim \mathcal{N}(x_i^\top w, \sigma^2)

Тогда правдоподобие всей выборки:

p(y | X, \theta) = \prod_i \mathcal{N}(y_i | x_i^\top w, \sigma^2)

Берём логарифм:

\log p(y | X, \theta) = \sum_i \log \left( \frac{1}{\sqrt{2\pi} \sigma} \exp\left( -\frac{(y_i - x_i^\top w)^2}{2\sigma^2} \right) \right)

Раскрываем сумму:

= \sum_i \left( -\frac{1}{2} \log(2\pi) - \log \sigma - \frac{(y_i - x_i^\top w)^2}{2\sigma^2} \right)

→ максимизация логарифма правдоподобия эквивалентна минимизации:

\sum_i (y_i - x_i^\top w)^2


✅ Вывод:

Метод максимального правдоподобия (MLE) для линейной регрессии приводит к функции потерь MSE — среднеквадратичной ошибке.

Регуляризация (например, Ridge(=L_2)) возникает при добавлении априорного распределения на веса — это уже MAP, не MLE.


✍ MLE и логистическая регрессия

В бинарной классификации целевая переменная yᵢ ∈ {0, 1} — для отклонения используем распределние Бернули.

Предполагаем:

P(y_i = 1 | x_i, w) = \sigma(x_i^\top w) = \frac{1}{1 + e^{-x_i^\top w}}

Правдоподобие всей выборки:

L(w) = \prod_i \left[ \sigma(x_i^\top w) \right]^{y_i} \cdot \left[ 1 - \sigma(x_i^\top w) \right]^{1 - y_i}

Логарифм правдоподобия:

\log L(w) = \sum_i \left[ y_i \log \sigma(x_i^\top w) + (1 - y_i) \log (1 - \sigma(x_i^\top w)) \right]


✅ Вывод:

Максимизация логарифма правдоподобия ⇔ минимизация log-loss (логистической функции потерь)


✍ Что меняется с регуляризацией: MAP (Maximum A Posteriori)

Добавим априорное распределение на параметры:

  • L2-регуляризация ⇔ априорное w ∼ 𝒩(0, λ⁻¹I)

  • L1-регуляризация ⇔ априорное w ∼ Laplace(0, b)

MAP-оценка:

\log P(w | X, y) ∝ \log L(w) + \log P(w)

→ Это и есть MLE + регуляризация:

  • MLE ⇔ логлосс

  • MAP ⇔ логлосс + регуляризация

💻 Баесовский вывод двух нормальных распределений

Задача

Пусть параметр w неизвестен и:

  • Prior: w \sim \mathcal{N}(\mu_0, \sigma_0^2)

  • Likelihood: y \sim \mathcal{N}(\mu_1, \sigma_1^2) — наблюдение, связанное с w

Найти posterior P(w \mid y)

📐 Шаг 1: формула Байеса

По определению:

P(w | y) \propto P(y | w) \cdot P(w)

Логарифмируем обе части:

\log P(w | y) = \log P(y | w) + \log P(w) + \text{const}

🧮 Шаг 2: подставляем нормальные распределения

  1. Prior:

\log P(w) = -\frac{1}{2\sigma_0^2} (w - \mu_0)^2 + \text{const}

  1. Likelihood (в терминах w, фиксируя y = \mu_1):

\log P(y | w) = -\frac{1}{2\sigma_1^2} (w - \mu_1)^2 + \text{const}

📉 Шаг 3: складываем логарифмы

\log P(w | y) = -\frac{1}{2\sigma_0^2} (w - \mu_0)^2 - \frac{1}{2\sigma_1^2} (w - \mu_1)^2 + \text{const}

Это — квадратичная функция по w, то есть логарифм нормального распределения. Следовательно, сам posterior — тоже нормальный:

P(w | y) = \mathcal{N}(\mu_{\text{post}}, \sigma_{\text{post}}^2)

✅ Вывод: параметры апостериорного распределения

\sigma_{\text{post}}^2 = \left( \frac{1}{\sigma_0^2} + \frac{1}{\sigma_1^2} \right)^{-1}\mu_{\text{post}} = \sigma_{\text{post}}^2 \left( \frac{\mu_0}{\sigma_0^2} + \frac{\mu_1}{\sigma_1^2} \right)


Отрисуем!

import numpy as np import matplotlib.pyplot as plt from scipy.stats import norm  # Ось параметра w w = np.linspace(-5, 5, 500)  # Заданные параметры mu0, sigma0 = 0, 1     # prior: N(0, 1) mu1, sigma1 = 2, 1     # likelihood: N(2, 1)  # Распределения prior = norm.pdf(w, loc=mu0, scale=sigma0) likelihood = norm.pdf(w, loc=mu1, scale=sigma1)  # Постериорное распределение — аналитически sigma_post_sq = 1 / (1/sigma0**2 + 1/sigma1**2) mu_post = sigma_post_sq * (mu0/sigma0**2 + mu1/sigma1**2) posterior = norm.pdf(w, loc=mu_post, scale=np.sqrt(sigma_post_sq))  # Визуализация plt.figure(figsize=(10, 6)) plt.plot(w, prior, label=f"Prior N({mu0}, {sigma0**2})", color='green') plt.plot(w, likelihood, label=f"Likelihood N({mu1}, {sigma1**2})", color='blue') plt.plot(w, posterior, label=f"Posterior N({mu_post:.2f}, {sigma_post_sq:.2f})", color='red')  plt.axvline(mu0, color='green', linestyle=':') plt.axvline(mu1, color='blue', linestyle=':') plt.axvline(mu_post, color='red', linestyle='--', label=f"MAP = {mu_post:.2f}")  plt.title("Байесовский вывод: Posterior = Prior × Likelihood") plt.xlabel("w") plt.ylabel("Плотность") plt.legend() plt.grid(True) plt.tight_layout() plt.show() 

На самом деле, даже для двух многмерных нормальных распределений с разными дисперсиями — верно следующее, их апостериальное распределение тоже нормальное!

4. Наивный байесовский классификатор

📌 Краткий ответ

Наивный байесовский классификатор предполагает, что все признаки условно независимы при фиксированном классе:

P(y | x_1, ..., x_d) ∝ P(y) \cdot \prod_{i=1}^d P(x_i | y)

Обучение: оцениваем P(y) и P(xᵢ | y) по каждому признаку.
Работает быстро, устойчив к малым выборкам, можно задавать разные распределения.

🔬 Подробный разбор

В логарифмической форме:

\log P(y | x) ∝ \log P(y) + \sum_i \log P(x_i | y)

Можно использовать:

  • Гауссовское распределениеGaussianNB

  • Ядерную оценку плотности (KDE) — сглаженные вероятности

  • Экспоненциальное, Лапласовское и др.

Преимущество подхода: можно подставлять разные распределения под разные признаки.

💻 Наивный Баейс на практике с разными распределениями признаков (KDE)

Полная тетрадка тут.

Для простоты будем работать не со всеми 4 признаками, а с двумя!

import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.neighbors import KernelDensity from sklearn.metrics import accuracy_score from sklearn.naive_bayes import GaussianNB from scipy.special import logsumexp  # --- 1. Загрузка и подготовка данных iris = load_iris() X = iris.data[:, [2, 3]]  # два признака: длина и ширина лепестка y = iris.target feature_names = np.array(iris.feature_names)[[2, 3]] class_names = iris.target_names  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) 
# --- 2. Обёртка KDE class KDEWrapper:     def __init__(self, data):         self.kde = KernelDensity(kernel='gaussian', bandwidth=0.2).fit(data[:, None])      def logpdf(self, x):         return self.kde.score_samples(x[:, None])       #  --- 3. NaiveBayes из тетрадки class NaiveBayes:     def fit(self, X, y, sample_weight=None, distributions=None):         self.unique_labels = np.unique(y)         if distributions is None:             distributions = [KDEWrapper] * X.shape[1]         assert len(distributions) == X.shape[1]         self.conditional_feature_distributions = {}         for label in self.unique_labels:             dists = []             for i in range(X.shape[1]):                 dists.append(distributions[i](X[y == label, i]))             self.conditional_feature_distributions[label] = dists         self.prior_label_distibution = {l: np.mean(y == l) for l in self.unique_labels}      def predict_log_proba(self, X):         log_proba = np.zeros((X.shape[0], len(self.unique_labels)))         for i, label in enumerate(self.unique_labels):             for j in range(X.shape[1]):                 log_proba[:, i] += self.conditional_feature_distributions[label][j].logpdf(X[:, j])             log_proba[:, i] += np.log(self.prior_label_distibution[label])         log_proba -= logsumexp(log_proba, axis=1)[:, None]         return log_proba      def predict(self, X):         return self.unique_labels[np.argmax(self.predict_log_proba(X), axis=1)] 
# --- 4. Обучение моделей model_kde = NaiveBayes() model_kde.fit(X_train, y_train, distributions=[KDEWrapper, KDEWrapper]) y_pred_kde = model_kde.predict(X_test)  model_gnb = GaussianNB() model_gnb.fit(X_train, y_train) y_pred_gnb = model_gnb.predict(X_test)  print("KDE NB Accuracy:", accuracy_score(y_test, y_pred_kde)) print("GaussianNB Accuracy:", accuracy_score(y_test, y_pred_gnb))  # KDE NB Accuracy: 1.0 # GaussianNB Accuracy: 1.0 
# --- 5. Визуализация KDE-плотностей def plot_kde_and_gaussian_densities(X_data, y_data):     fig, axes = plt.subplots(1, 2, figsize=(12, 4))          # Обучим GaussianNB — он сам оценит параметры     gnb = GaussianNB()     gnb.fit(X_data, y_data)      for i in range(X_data.shape[1]):         ax = axes[i]         for label in np.unique(y_data):             x_vals = X_data[y_data == label, i]             grid = np.linspace(x_vals.min() * 0.9, x_vals.max() * 1.1, 500)              # --- KDE             kde = KernelDensity(kernel='gaussian', bandwidth=0.2).fit(x_vals[:, None])             ax.plot(grid, np.exp(kde.score_samples(grid[:, None])), label=f'{class_names[label]} (KDE)', linestyle='-')              # --- Gauss via GaussianNB             mu = gnb.theta_[label, i]             sigma = np.sqrt(gnb.var_[label, i])             ax.plot(grid, norm.pdf(grid, mu, sigma), label=f'{class_names[label]} (Gauss)', linestyle='--')           ax.set_title(f'Плотности для {feature_names[i]}')         ax.set_xlabel('Значение признака')         ax.set_ylabel('Плотность')         ax.legend()         ax.grid()      plt.tight_layout()     plt.show()  # --- 6. Визуализация границ решений def plot_decision_boundary(X_data, y_data, model, title):     x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5     y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5     xx, yy = np.meshgrid(np.linspace(x_min, x_max, 300),                          np.linspace(y_min, y_max, 300))     grid = np.c_[xx.ravel(), yy.ravel()]     Z = model.predict(grid).reshape(xx.shape)      plt.figure(figsize=(8, 6))     plt.contourf(xx, yy, Z, alpha=0.3, cmap='Accent')     plt.contour(xx, yy, Z, levels=np.arange(0, 4), colors='k', linewidths=0.5)     for label in np.unique(y_train):         plt.scatter(X_data[y_data == label, 0], X_data[y_data == label, 1],label=class_names[label], s=40)     plt.xlabel(feature_names[0])     plt.ylabel(feature_names[1])     plt.title(title)     plt.legend()     plt.grid(True)     plt.tight_layout()     plt.show()  # train plot_kde_and_gaussian_densities(X_train, y_train) plot_decision_boundary(X_train, y_train, model_kde, "Наивный Байес с KDE") plot_decision_boundary(X_train, y_train, model_gnb, "GaussianNB (Гауссовский Наивный Байес)") # test plot_kde_and_gaussian_densities(X_test, y_test) plot_decision_boundary(X_test, y_test, model_kde, "Наивный Байес с KDE") plot_decision_boundary(X_test, y_test, model_gnb, "GaussianNB (Гауссовский Наивный Байес)") 

5. Метод ближайших соседей

📌 Краткий ответ

Метод k ближайших соседей (k-NN) — это ленивый классификатор, который:

  • не обучается явно

  • при предсказании ищет k ближайших объектов в обучающей выборке

  • голосует за класс большинства (или усредняет — в регрессии).

Работает по метрике (например, евклидовой), чувствителен к масштабу и шуму.

🔬 Подробный разбор

При классификации:

ŷ(x) = argmax_c ∑ I(yᵢ = c) для xᵢ ∈ N_k(x) 

Плюсы:

  • не требует обучения,

  • хорошо работает на небольших данных.

Минусы:

  • не масштабируется (хранит всё),

  • чувствителен к размерности и шуму,

  • требует нормализации признаков.

Гиперпараметры:

  • k — число соседей (подбирается на валидации),

  • metric — метрика расстояния (евклидово, косинусное и т.д.)

💻 Пишем свой KNN и сравниваемся с библиотечным на MNIST
import numpy as np from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score from scipy.stats import mode  # --- 1. Загружаем данные digits = load_digits() X = digits.data y = digits.target  # Масштабирование X = StandardScaler().fit_transform(X)  # Разделение X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)  # --- 2. Своя реализация k-NN (евклидовая метрика) class MyKNN:     def __init__(self, n_neighbors=5):         self.k = n_neighbors      def fit(self, X, y):         self.X_train = X         self.y_train = y      def predict(self, X):         predictions = []         for x in X:             dists = np.linalg.norm(self.X_train - x, axis=1)             nearest = np.argsort(dists)[:self.k]             labels = self.y_train[nearest]             pred = mode(labels, keepdims=False).mode              predictions.append(pred)         return np.array(predictions)  # --- 3. Обучение и сравнение # sklearn sk_knn = KNeighborsClassifier(n_neighbors=5) sk_knn.fit(X_tr, y_tr) y_pred_sk = sk_knn.predict(X_te) acc_sk = accuracy_score(y_te, y_pred_sk)  # наш my_knn = MyKNN(n_neighbors=5) my_knn.fit(X_tr, y_tr) y_pred_my = my_knn.predict(X_te) acc_my = accuracy_score(y_te, y_pred_my)  assert np.isclose(acc_sk, acc_my, rtol=1e-6), 'Точности не совпдают!' print(f"{acc_sk=} {acc_my=}") # acc_sk=0.9777777777777777 acc_my=0.9777777777777777 

📊 Глава 2: Почему линейная модель — это не просто прямая

6. Линейная регрессия. Формулировка задачи для случая функции потерь MSE. Аналитическое решение. Теорема Гаусса-Маркова. Градиентный подход в линейной регрессии.

📌 Краткий ответ

Линейная регрессия минимизирует MSE:

L(w) = \|Xw - y\|^2

  • Аналитически: \hat{w} = (X^\top X)^{-1} X^\top y

  • Теорема Гаусса-Маркова: это наилучшая линейная несмещённая оценка при стандартных предположениях (BLUE)

  • При больших данных: используется градиентный спуск

🔬 Подробный разбор

📌 Постановка задачи

У нас есть:

  • X \in \mathbb{R}^{n \times d} — матрица признаков;

  • y \in \mathbb{R}^n — целевая переменная;

  • w \in \mathbb{R}^d — веса модели.

Цель: минимизировать среднеквадратичную ошибку:

L(w) = \|Xw - y\|^2 = (Xw - y)^\top (Xw - y)

📌 Вывод аналитического решения

Выпишем градиент по w:

\nabla_w L(w) = 2 X^\top (Xw - y)

Приравниваем к нулю:

X^\top (Xw - y) = 0

Раскрываем скобки:

X^\top X w = X^\top y

Предполагая, что X^\top X обратима:

\hat{w} = (X^\top X)^{-1} X^\top y

📌 Теорема Гаусса-Маркова (формулировка)

Если:

  1. модель линейна по параметрам: y = Xw + \varepsilon;

  2. ошибки \varepsilon имеют нулевое среднее;

  3. одинаковую дисперсию \text{Var}(\varepsilon) = \sigma^2 I;

  4. некоррелированы между собой;

→ тогда \hat{w} (из нормального уравнения) — наилучшая линейная несмещённая оценка (BLUE).

Термин BLUE (Best Linear Unbiased Estimator) — это сокращение:

  1. Linear (линейная):
    Оценка \hat{w} — это линейная функция от y:

    \hat{w} = A y

    где A = (X^\top X)^{-1} X^\top

  2. Unbiased (несмещённая):
    Среднее значение оценки совпадает с истинным параметром:

    \mathbb{E}[\hat{w}] = w

  3. Best (наилучшая):
    Из всех линейных и несмещённых оценок, \hat{w} имеет наименьшую дисперсию:

    \text{Var}(\hat{w}) \text{ минимальна среди всех линейных несмещённых оценок}

💻 Аналитическое и градиентное решение поиска весов линейной модели + график
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_regression from sklearn.metrics import mean_squared_error  # --- Данные X_raw, y = make_regression(n_samples=300, n_features=1, noise=15, random_state=42) X = np.hstack([X_raw, np.ones((X_raw.shape[0], 1))])  # добавим bias  # --- Аналитическое решение w_analytic = np.linalg.inv(X.T @ X) @ X.T @ y y_pred_analytic = X @ w_analytic  # --- Градиентный спуск w = np.zeros(X.shape[1]) lr = 0.01 losses = []  for _ in range(1000):     grad = 2 * X.T @ (X @ w - y) / len(y)     w -= lr * grad     losses.append(mean_squared_error(y, X @ w))  y_pred_gd = X @ w  # --- Сравнение print("MSE (аналитика):", mean_squared_error(y, y_pred_analytic)) print("MSE (градиент):", mean_squared_error(y, y_pred_gd))  # --- Визуализация plt.figure(figsize=(10, 5))  # 1. Предсказания plt.subplot(1, 2, 1) plt.scatter(X_raw, y, s=20, alpha=0.6, label='Данные') plt.plot(X_raw, y_pred_analytic, label='Аналитическое решение', color='green') plt.plot(X_raw, y_pred_gd, label='Градиентный спуск', color='red', linestyle='--') plt.title("Сравнение решений") plt.xlabel("X") plt.ylabel("y") plt.legend() plt.grid()  # 2. Потери во времени plt.subplot(1, 2, 2) plt.plot(losses, label="MSE (градиент)") plt.title("Сходимость градиента") plt.xlabel("Итерации") plt.ylabel("MSE") plt.grid() plt.tight_layout() plt.show() # MSE (аналитика): 230.84267462302407 # MSE (градиент): 230.84267462302407 

7. Регуляризация в линейных моделях: L_1 ,L_2 их свойства. Вероятностная интерпретация.

📌 Краткий ответ

Регуляризация — это добавка к функции потерь, которая ограничивает рост весов и борется с переобучением.

  • L2-регуляризация (Ridge):

    \text{Loss}(w) = \text{MSE}(w) + \lambda \|w\|_2^2

  • L1-регуляризация (Lasso):

    \text{Loss}(w) = \text{MSE}(w) + \lambda \|w\|_1

  • L2 сглаживает и уменьшает веса

  • L1 приводит к разреженным решениям (обнуляет ненужные признаки)

🔬 Подробный разбор

L_2 — Ridge, L_1 — Lasso, Elastic Net — комбинация.

📌 Вероятностная интерпретация

Добавление регуляризатора эквивалентно введению априорного распределения на параметры (подробнее в 3 вопросе о MLE):

  • L2 = Gaussian prior:

    P(w) \sim \mathcal{N}(0, \sigma^2 I)\Rightarrow \text{MAP} \propto \log L(w) - \lambda \|w\|_2^2

  • L1 = Laplace prior:

    P(w) \sim \text{Laplace}(0, b)\Rightarrow \text{MAP} \propto \log L(w) - \lambda \|w\|_1

→ То есть регуляризация = байесовская MAP-оценка, если мы знаем prior на веса.


Почему при зануляются веса?

Очень популярный и важный вопрос! Изобразим уровни потерь по отдельности двух частей лосса!

Предпложим противное. Пусть t^* опитимум пересечения и он не на осях координат. Из-за выпуклости двух фигур, найдется пересечения внутри, а по нему можно уже подняться вверх, сохранив ошибку по L_1 и уменьшить MSE ! Второй заумный аргумент .

Упрощение 2-аргумента: две фигуры выпуклые и имеют единственную касательную (помимо L_1 в точках на осях!), тогда в точке касания можно провести разделяющую прямую!

А это значит, что оси у элипса у MSE параллельны фиксированному направлению, а вероятность таких направлений (на одну размерность меньше=) равна нулю!

💻 Сравниваем веса моделей с L1 и L2

Обучим две модельки с разными регулизаторами на данных с 8 из 10 шумных признаками. В идеали избавиться(иметь вес ноль) от всех неинформативных признаков!

# Генерируем данные: 2 полезных признака + 8 шумовых  from sklearn.linear_model import Ridge, Lasso from sklearn.metrics import mean_squared_error import numpy as np import matplotlib.pyplot as plt  # --- Параметры n_samples = 100 n_features = 10 n_informative = 2  # только два признака "полезные"  # --- Генерация данных np.random.seed(42) X = np.random.randn(n_samples, n_features) true_coefs = np.zeros(n_features) true_coefs[:n_informative] = [3, -2]  # только первые 2 признака значимы  # Целевая переменная с шумом y = X @ true_coefs + np.random.normal(0, 1.0, size=n_samples)  # --- Обучение моделей ridge = Ridge(alpha=1.0) lasso = Lasso(alpha=0.1)  ridge.fit(X, y) lasso.fit(X, y)  # --- Сравнение весов x_idx = np.arange(n_features) plt.figure(figsize=(10, 4)) plt.stem(x_idx, true_coefs, linefmt="gray", markerfmt="go", basefmt=" ", label="True") plt.stem(x_idx, ridge.coef_, linefmt="b-", markerfmt="bo", basefmt=" ", label="Ridge") plt.stem(x_idx, lasso.coef_, linefmt="r-", markerfmt="ro", basefmt=" ", label="Lasso") plt.xticks(ticks=x_idx) plt.title("L1 vs L2: 2 информативных признака, остальные шум") plt.xlabel("Индекс признака") plt.ylabel("Вес") plt.legend() plt.tight_layout() plt.show()  # --- Подсчёт зануленных весов ridge_zeros = np.sum(np.abs(ridge.coef_) < 1e-4) lasso_zeros = np.sum(np.abs(lasso.coef_) < 1e-4) print(f"{ridge_zeros=}, f{lasso_zeros=}") # ridge_zeros=np.int64(0), flasso_zeros=np.int64(5) 

8. Логистическая регрессия. Эквивалентность подходов MLE и минимизации логистических потерь.

📌 Краткий ответ

Логистическая регрессия — это модель предсказывающая вероятность класса y = 1:

P(y = 1 | x) = \sigma(x^\top w), \quad \sigma(z) = \frac{1}{1 + e^{-z}} 

MLE: максимизация логарифма правдоподобия ⇔ минимизация log-loss (обычно для бинарной классификации) = cross-entropy-loss (обычно для мультиклассовой классификации)

\log L(w) = \sum_i y_i \log p_i + (1 - y_i) \log(1 - p_i)

или

\mathcal{L}(W) = - \sum_{i=1}^{n} \sum_{k=1}^{K} p_{i, k} \cdot \log \hat{p}_{i,k} | обычно, p_{i_{fix}, k} — ровно одна 1 и остальные нули.

🔬 Подробный разбор

📌 Вывод: MLE для логистической регрессии

Обозначим:

  • y_i \in \{0, 1\}

  • p_i = \sigma(x_i^\top w)

Правдоподобие:

L(w) = \prod_i p_i^{y_i} (1 - p_i)^{1 - y_i} 

Логарифмируем:

\log L(w) = \sum_i y_i \log p_i + (1 - y_i) \log(1 - p_i) 

Подставляем p_i = \sigma(x_i^\top w):

И получаем логистическую функцию потерь:

\mathcal{L}(w) = - \sum_i \left[   y_i \log \sigma(x_i^\top w) + (1 - y_i) \log(1 - \sigma(x_i^\top w)) \right] 
💻 Почему сырая линейка плоха в классификации? А лог-рег хорош?

Предлагаю разобрать пример, в котором лог-рег прекрасно абсолютно предсказывает вероятности, чем линейка не может похвастаться. Иногда нужно не просто факт класса сказать, а и вероятность!

Но дальше мы узнаем о SVM, и увидим что не обязательно приводить выход модели в диапозон [0, 1] !

# Пример: класс 0 сконцентрирован в одном месте, класс 1 — сильно растянут вправо  # Класс 0 — 50 точек около x = 0 X0 = np.random.normal(loc=0, scale=0.5, size=(50, 1)) y0 = np.zeros(50)  # Класс 1 — 50 точек с растущим x (от 10 до 500) # X1 = np.linspace(5, 25, 10).reshape(-1, 1) X1 = np.linspace(10, 500, 10).reshape(-1, 1) y1 = np.ones(10)  # Объединяем X_all = np.vstack([X0, X1]) y_all = np.concatenate([y0, y1])  # Обучаем модели linreg = LinearRegression().fit(X_all, y_all) logreg = LogisticRegression().fit(X_all, y_all)  # Предсказания на сетке x_grid = np.linspace(-2, 30, 500).reshape(-1, 1) lin_preds = linreg.predict(x_grid) log_probs = logreg.predict_proba(x_grid)[:, 1]  # Визуализация plt.figure(figsize=(10, 5)) plt.scatter(X0, y0, color='blue', label='Класс 0 (скученный)', alpha=0.7) plt.scatter(X1, y1, color='orange', label='Класс 1 (удалённый)', alpha=0.9) plt.plot(x_grid, lin_preds, color='green', linestyle='--', label='Linear Regression') plt.plot(x_grid, log_probs, color='black', label='Logistic Regression') plt.xlabel("x") plt.ylabel("Предсказание / Вероятность") plt.title("Линейная vs логистическая регрессия при удалённых объектах класса 1") plt.ylim(-0.1, 1.1) plt.legend() plt.grid(True) plt.tight_layout() plt.show() 

9. Многоклассовая классификация. Один-против-одного, один-против-всех, их свойства.

📌 Краткий ответ
  • One-vs-Rest (OvR): обучаем K бинарных моделей «класс vs остальные», выбираем класс с макс. откликом.

  • One-vs-One (OvO): обучаем \frac{K(K-1)}{2} моделей по парам классов, предсказание — по большинству голосов.

  1. При предсказании инферяться все модели!

  2. В One-vs-One не учитываются вероятности лишь факт победы.

🔬 Подробный разбор

📌 One-vs-Rest (OvR)

Обучение:

  • K бинарных моделей f_k(x), каждая отличает класс k от остальных

Предсказание:

  • Вычисляем отклики f_0(x), f_1(x), \dots, f_{K-1}(x)

  • Выбираем класс с максимальным значением:

    \hat{y} = \arg\max_k f_k(x)

Если модель выдаёт вероятности P(y = k \mid x), выбираем по ним.

📌 One-vs-One (OvO)

Обучение:

  • Строим классификаторы для всех пар классов:

    f_{i,j}(x) \text{ обучается на классах } i \text{ и } j

    Всего \frac{K(K-1)}{2} моделей.

Предсказание:

  • Каждый классификатор голосует: f_{i,j}(x) \in \{i, j\}

  • Считаем число голосов за каждый класс

  • Итог:

    \hat{y} = \arg\max_k \text{(кол-во голосов за } k)

Голоса — дискретные, уверенность моделей не используется.

💻 Сравнение One-vs-Rest и One-vs-One на Iris
from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.multiclass import OneVsRestClassifier, OneVsOneClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, confusion_matrix import numpy as np import pandas as pd from IPython.display import display  # --- 1. Данные X, y = load_iris(return_X_y=True) X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)  # --- 2. Модель base_model = LogisticRegression(max_iter=1000)  # --- 3. One-vs-Rest (OvR) clf_ovr = OneVsRestClassifier(base_model).fit(X_tr, y_tr) y_pred_ovr = clf_ovr.predict(X_te)  # --- 4. One-vs-One (OvO) clf_ovo = OneVsOneClassifier(base_model).fit(X_tr, y_tr) y_pred_ovo = clf_ovo.predict(X_te)  # --- 5. Confusion matrices cm_ovr = confusion_matrix(y_te, y_pred_ovr) cm_ovo = confusion_matrix(y_te, y_pred_ovo)  print("Confusion Matrix (OvR):") print(cm_ovr)  print("\nConfusion Matrix (OvO):") print(cm_ovo)  # --- 6. Разбиения для OvR ovr_split = pd.DataFrame({     'Класс': list(range(len(clf_ovr.estimators_))),     'Положительных': [(y_tr == k).sum() for k in range(len(clf_ovr.estimators_))],     'Отрицательных': [(y_tr != k).sum() for k in range(len(clf_ovr.estimators_))],     'Всего': [len(y_tr)] * len(clf_ovr.estimators_) })  print("\nOvR — разбивка по классам:") display(ovr_split)  # --- 7. Разбиения для OvO ovo_pairs = [(est.classes_[0], est.classes_[1]) for est in clf_ovo.estimators_]  ovo_data = [] for a, b in ovo_pairs:     count_a = np.sum(y_tr == a)     count_b = np.sum(y_tr == b)     total = count_a + count_b     ovo_data.append({         'Пара классов': f"{a} vs {b}",         f"#{a}": count_a,         f"#{b}": count_b,         'Суммарно': total     })  ovo_split = pd.DataFrame(ovo_data)  print("\nOvO — разбивка по парам классов:") display(ovo_split)  # --- 8. Accuracy summary print(f"\nOvR Accuracy: {accuracy_score(y_te, y_pred_ovr):.3f}") print(f"OvO Accuracy: {accuracy_score(y_te, y_pred_ovo):.3f}")  

Видно что датасет достаточно хороший игрушечный, тут и данные разделены хорошо и по классам все сбалансированно (и классов немного).

10. Метод опорных векторов. Задача оптимизации для SVM. Трюк с ядром. Свойства ядра.

📌 Краткий ответ

SVM (support vector machine) — это алгоритм решает задачу классификации («линейная классификация»), который ищет гиперплоскость, максимально разделяющую классы с зазором (margin).

Он решает задачу максимизации отступа, то есть делает так, чтобы:

  • все объекты лежали как можно дальше от границы (реализуется ядром, скалярным произведением сонаправленностью <y, \hat{y}>)

  • и при этом допускались ошибки для равномерного отступа (через мягкие штрафы) (реализуется добавлением зазора=константы 1 — M).

  • ядра можно брать разные — не обязательно линейное

Функция потерь (hinge-loss) устроена так, чтобы:

  • не штрафовать объекты с отступом \ge 1,

  • и наказывать только те, которые «лезут» в буферную зону или ошибаются (=либо неверные, либо неуверенно правильные!):

\mathcal{L}(w) = \frac{1}{n} \sum \max(0, 1 - y_i (w^\top x_i + b)) + \frac{\lambda}{2} \|w\|^2

Чем, же это лучше чем просто линейная регрессия в классификации? А тем, что SVM решает задачу разделить данные, а линейная регресия старается провести через них!

🔬 Подробный разбор

📌 Модель

Классификатор:

\hat{y}(x) = \text{sign}(w^\top x + b)

Отступ (margin):

M_i = y_i (w^\top x_i + b)

Чем больше M_i, тем выше уверенность в классификации.


📌 Целевая функция (hinge loss)

SVM минимизирует:

\mathcal{L}(w) = \frac{1}{n} \sum_{i=1}^n \max(0, 1 - y_i(w^\top x_i + b)) + \frac{\lambda}{2} \|w\|^2

  • Первый член — штраф за малый отступ (ошибки или «почти ошибки»)

  • Второй — регуляризация (контроль за нормой w)


📌 Ядровой трюк (kernel trick)

Вместо линейного x \cdot x', используем:

K(x, x') = \langle \phi(x), \phi(x') \rangle

→ Не нужно явно строить \phi(x), а граница может быть нелинейной.


📌 Примеры ядер

Ядро

Формула

Линейное

K(x, x') = x^\top x'

Полиномиальное

K(x, x') = (x^\top x' + c)^d

RBF (Гаусс)

K(x, x') = \exp(-\gamma |x - x'|^2)

✅ Свойства допустимого ядра (ядровой функции)

Функция K(x, x') — допустимое ядро, если оно:

  1. Симметрична:

    K(x, x') = K(x', x)

  2. Положительно полуопределённая (PSD):
    Для любых x_1, \dots, x_n \in \mathbb{R}^d и любых весов \alpha_1, \dots, \alpha_n \in \mathbb{R} выполняется:

    \sum_{i=1}^n \sum_{j=1}^n \alpha_i \alpha_j K(x_i, x_j) \ge 0

    Это значит: матрица Грама K на любом наборе точек — положительно полуопределённая.

Почему это важно?

Если K — валидное ядро, то по теореме Мерсера:

K(x, x') = \langle \phi(x), \phi(x') \rangle

для некоторого отображения \phi(\cdot) в (возможно бесконечномерное) пространство признаков.

→ Это делает метод SVM с ядром линейным в этом скрытом пространстве, без явного вычисления \phi(x).

💻 Сравнения разных SVM ядер: linear vs poly vs RBF
from sklearn.datasets import make_classification from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt import numpy as np  # --- 1. Данные X, y = make_classification(n_samples=300, n_features=2, n_redundant=0,                            n_clusters_per_class=1, class_sep=1.0, random_state=42) X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)  # --- 2. Модели clf_linear = SVC(kernel='linear', C=1).fit(X_tr, y_tr) clf_rbf = SVC(kernel='rbf', gamma=1, C=1).fit(X_tr, y_tr) clf_poly = SVC(kernel='poly', degree=3, C=1).fit(X_tr, y_tr)   # --- 3. Визуализация def plot_decision_boundary(model, X, y, ax, title):     h = 0.02     x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1     y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1     xx, yy = np.meshgrid(np.arange(x_min, x_max, h),                          np.arange(y_min, y_max, h))     Z = model.predict(np.c_[xx.ravel(), yy.ravel()])     Z = Z.reshape(xx.shape)      ax.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.coolwarm)     ax.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.coolwarm, edgecolors='k')     ax.set_title(title)     ax.set_xlabel("x₁")     ax.set_ylabel("x₂")     ax.grid(True)  # --- 4. Графики fig, axes = plt.subplots(1, 3, figsize=(12, 5)) plot_decision_boundary(clf_linear, X_te, y_te, axes[0], "Линейный SVM") plot_decision_boundary(clf_poly, X_te, y_te, axes[1], "Полиномиальный 3-й степени SVM") plot_decision_boundary(clf_rbf, X_te, y_te, axes[2], "RBF SVM") plt.tight_layout() plt.show()   y_pred_linear = clf_linear.predict(X_te) y_pred_poly = clf_poly.predict(X_te) y_pred_rbf = clf_rbf.predict(X_te)  # Accuracy acc_linear = accuracy_score(y_te, y_pred_linear) acc_poly = accuracy_score(y_te, y_pred_poly) acc_rbf = accuracy_score(y_te, y_pred_rbf) print(f"Linear SVM Accuracy: {acc_linear:.3f}") print(f"Polynomial SVM Accuracy: {acc_poly:.3f}") print(f"RBF SVM Accuracy: {acc_rbf:.3f}") # Linear SVM Accuracy: 0.944 # Polynomial SVM Accuracy: 0.911 # RBF SVM Accuracy: 0.967 

Что дальше?

Учимся быстро и понятно рассказывать. Для этого проговариваем много раз. Рассказываем друзьям, либо записываем себе и слушаем.

Пока готовимся, сохраняем каверзные вопросы, на которые непросто дать верные/легкии ответы. Сначала основное, потом остальное!

Материалы


В следующей части продолжим — будут PCA, Bias–variance tradeoff, деревья, ансамбли, бустинг, и глубокое обучение. Пока подписывайтесь и делитесь своими находками!


ссылка на оригинал статьи https://habr.com/ru/articles/918438/