Кафедра ШІзики

Перенавчання vs Недонавчання - Битва, яку мусить виграти кожен ML-інженер

Кафедра ШІзики

Автор

30 хвилин

Час читання

12.01.2025

Дата публікації

Рівень:
Середній
Теги: #перенавчання #недонавчання #зміщення-дисперсія #регуляризація #складність-моделі

Перенавчання vs Недонавчання: Битва, яку мусить виграти кожен ML-інженер

Ідеальна модель, яка провалилася

Пам’ятаєте, як у Статті 7 ваше дерево рішень досягло 100% точності на тренувальних даних?

Ви, напевно, думали: “Ідеально! Запускаємо!” 🚀

Але потім прийшли тестові дані… 77.5% точності. Дерево, яке здавалося геніальним, насправді було шахраєм — воно запам’ятало відповіді замість того, щоб вивчити закономірності.

Це не просто баг у коді. Це перенавчання (overfitting) — тихий вбивця моделей машинного навчання у продакшені. І зараз це стане дуже реальним для піцерії Мами ML.


🍕 Піцерійна катастрофа: Ранок понеділка

6 ранку понеділка. Марія, власниця піцерії “Мама ML”, перевіряє свою нову “суперточну” модель прогнозування попиту.

Прогноз моделі: 150 піц на обідній пік.

Марія довіряє цифрам. Зрештою, модель мала 98% точності на історичних даних! Вона замовляє додаткове тісто, викликає двох додаткових працівників і готується до напливу клієнтів.

Реальні замовлення до 14:00: 82 піци.

Марія дивиться на 68 незапечених піц — викинуті інгредієнти. Двоє зайвих працівників стоять без діла. $340 збитків за одну обідню зміну.

“Але ж модель була на 98% точною!” — каже вона своєму племіннику-дата-сайєнтисту.

Він зітхає. “На ТРЕНУВАЛЬНИХ даних, тітко. Модель запам’ятала минулі патерни замість того, щоб навчитися передбачати майбутні.”

Це перенавчання. І сьогодні ми його виправимо.


Аналогія зі студентом: Розуміємо проблему

Перш ніж перейти до виправлень, давайте зрозуміємо, що насправді відбувається. Уявіть трьох різних студентів, які готуються до іспиту:

😰 Зубрило (Перенавчання)

  • Запам’ятовує кожну відповідь з практичних тестів слово в слово
  • Набирає 100% на практичних тестах — ідеально!
  • День іспиту: 55% — питання трохи інші, і він панікує
  • Проблема: Вивчив відповіді, а не концепції

Це модель піцерії Марії. Вона запам’ятала, що “15 березня 2024 = 147 піц” замість того, щоб навчитися “холодні п’ятниці = високий попит.”

😴 Ледар (Недонавчання)

  • Ледве вчиться, запам’ятовує лише “більшість відповідей — В”
  • Набирає 50% на практичних тестах — не дуже
  • День іспиту: 48% — приблизно те саме
  • Проблема: Не вивчив достатньо, щоб відповісти на що-небудь добре

Це була б модель, яка просто прогнозує “100 піц щодня” незалежно від умов.

🎯 Розумний студент (Ідеальний баланс)

  • Розуміє концепції та закономірності
  • Набирає 85% на практичних тестах — солідно
  • День іспиту: 83% — трохи нижче, але стабільно
  • Мета: Вивчити патерни, які працюють на нових, невідомих ситуаціях

Модель Марії повинна стати розумним студентом.


🔬 Розслідування катастрофи: Що пішло не так?

Давайте подивимося на реальні дані Марії, щоб зрозуміти проблему.

Дані

import numpy as np
import matplotlib.pyplot as plt

# Історичні дані продажів Марії
np.random.seed(42)
temperatures = np.linspace(5, 35, 30)  # Температура в Цельсіях
# Справжній патерн: Холодна погода = більше замовлень, з деякою кривизною
true_sales = 150 - 2 * temperatures + 0.05 * temperatures**2
actual_sales = true_sales + np.random.normal(0, 12, 30)  # Реальний шум

print("🌡️ Діапазон температур: 5°C до 35°C")
print(f"🍕 Діапазон продажів: {actual_sales.min():.0f} до {actual_sales.max():.0f} піц")

Три різні моделі

Подивімося, що сталося з трьома різними підходами:

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline

# Модель 1: Ледар (занадто проста)
model_slacker = make_pipeline(
    PolynomialFeatures(degree=1),  # Просто пряма лінія
    LinearRegression()
)

# Модель 2: Розумний студент (в самий раз)
model_smart = make_pipeline(
    PolynomialFeatures(degree=2),  # Схоплює криву
    LinearRegression()
)

# Модель 3: Зубрило (занадто складна)
model_memorizer = make_pipeline(
    PolynomialFeatures(degree=15),  # Звивається через кожну точку
    LinearRegression()
)

# Тренуємо всі моделі
X = temperatures.reshape(-1, 1)
for model in [model_slacker, model_smart, model_memorizer]:
    model.fit(X, actual_sales)

Інтерактивна демонстрація: Побачте самі

Використовуйте повзунок нижче, щоб змінити ступінь полінома. Спостерігайте, як модель трансформується від недонавчання до перенавчання:

Polynomial Fitting: Underfit vs Overfit / Поліноміальна апроксимація

1 (Linear) 2 (Quadratic) 15 (Very Complex)
Training Error
0.045
Test Error
0.052
Gap
0.007

🔍 Що ви бачите?

СтупіньЩо відбуваєтьсяДіагноз
1-2Пряма лінія, пропускає криву⚠️ Недонавчання — занадто проста
2-4Гарно схоплює патернХороша модель — в самий раз
10+Звивиста лінія через кожну точкуПеренавчання — запам’ятовує шум

Саме це сталося з моделлю Марії! Її племінник використав складну модель глибокого навчання, коли проста крива спрацювала б краще.


📊 Діагноз: Помилка на тренувальних vs тестових даних

Ось ключовий інсайт, який зберіг би Марії $340: завжди перевіряйте і тренувальну, І тестову продуктивність.

from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Розділяємо дані — 70% для тренування, 30% для тестування
X_train, X_test, y_train, y_test = train_test_split(
    X, actual_sales, test_size=0.3, random_state=42
)

results = []
for name, model in [("Ледар", model_slacker),
                     ("Розумний", model_smart),
                     ("Зубрило", model_memorizer)]:
    model.fit(X_train, y_train)

    train_rmse = np.sqrt(mean_squared_error(y_train, model.predict(X_train)))
    test_rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
    gap = test_rmse - train_rmse

    results.append((name, train_rmse, test_rmse, gap))

print("\n🍕 Діагностика моделі Марії")
print("=" * 65)
print(f"{'Модель':<12} {'Трен. помилка':>15} {'Тест. помилка':>15} {'Розрив':>12}")
print("-" * 65)
for name, train, test, gap in results:
    status = "✅" if gap < 5 else "⚠️" if gap < 15 else "❌"
    print(f"{name:<12} {train:>13.1f} {test:>15.1f} {gap:>10.1f}   {status}")

Результат:

🍕 Діагностика моделі Марії
=================================================================
Модель        Трен. помилка   Тест. помилка       Розрив
-----------------------------------------------------------------
Ледар               15.8            17.2          1.4   ✅
Розумний             9.2            10.5          1.3   ✅
Зубрило              0.1            48.3         48.2   ❌

💡 Відкриття:

Племінник Марії використав модель “Зубрило”, бо вона мала найнижчу тренувальну помилку (0.1 піци — майже ідеально!). Але подивіться на тестову помилку: 48.3 піци! І розрив 48.2!

Це характерна ознака перенавчання: Чудова тренувальна продуктивність, жахлива реальна продуктивність.

Діагностична таблиця

Ось як читати ознаки:

Трен. помилкаТест. помилкаРозривДіагнозДія
ВисокаВисокаМалийНедонавчанняЗбільшити складність
НизькаНизькаМалийХороша модельЗапускаємо! 🚀
Дуже низькаВисокаВеликийПеренавчанняСпростити або регуляризувати

⚖️ Компроміс зміщення-дисперсії

За перенавчанням та недонавчанням стоїть фундаментальне протиріччя, яке має розуміти кожен ML-інженер:

Зміщення (Bias): Проблема “Стабільно неправильно”

Зміщення — це помилка від надто спрощених припущень.

Приклад: Модель-ледар Марії припускає, що продажі піц — це пряма лінія від температури. Але реальний попит має криву! Модель стабільно неправильна в одному напрямку.

Ознаки:

  • Висока тренувальна помилка
  • Висока тестова помилка
  • Малий розрив між ними
  • Більше даних не допоможе

Виправлення: Використати складнішу модель, додати ознаки

Дисперсія (Variance): Проблема “Нестабільності”

Дисперсія — це помилка від чутливості до малих коливань у тренувальних даних.

Приклад: Модель-зубрило Марії кардинально змінюється, якщо додати або прибрати кілька точок даних. Вона нестабільна — занадто реагує на шум.

Ознаки:

  • Дуже низька тренувальна помилка
  • Висока тестова помилка
  • Великий розрив між ними
  • Більше даних ДОПОМОЖЕ!

Виправлення: Спростити модель, використати регуляризацію, отримати більше даних

Візуалізація компромісу

Загальна помилка = Зміщення² + Дисперсія + Незводимий шум

Ви не можете мінімізувати обидва! Дослідіть компроміс нижче:

Bias-Variance Tradeoff / Компроміс Зміщення-Дисперсії

Simple / Проста Complex / Складна
🎯 Bias² / Зміщення²
0.15
Systematic error from assumptions
📊 Variance / Дисперсія
0.10
Sensitivity to training data
📈 Total Error / Загальна помилка
0.25
Bias² + Variance + Noise

Quick Examples / Швидкі приклади:

🎯 Золота середина: Знайдіть рівень складності, де загальна помилка мінімальна. Зазвичай це десь посередині — не занадто проста, не занадто складна.


📈 Криві навчання: Ваш діагностичний інструмент

Криві навчання показують, як продуктивність вашої моделі змінюється при додаванні більше тренувальних даних. Вони як медичне сканування для здоров’я вашої моделі.

Learning Curves / Криві навчання

Select Scenario / Оберіть сценарій:

Як читати діагноз

Прокликайте три сценарії вище та зверніть увагу:

⚠️ Недонавчання (Високе зміщення):

  • Обидві криві високі та плоскі
  • Тренувальна та тестова помилки схожі
  • Більше даних не допоможе — модель фундаментально занадто проста
  • Рецепт: Збільшити складність

✅ Хороша модель (Збалансована):

  • Обидві криві сходяться з низькою помилкою
  • Малий розрив між тренувальною та тестовою
  • Модель добре узагальнює
  • Рецепт: Готово! Підналаштуйте, якщо потрібно

❌ Перенавчання (Висока дисперсія):

  • Великий розрив між кривими
  • Тренувальна помилка дуже низька, тестова висока
  • Криві можуть зійтися з більшою кількістю даних
  • Рецепт: Спростити, регуляризувати або отримати більше даних

Момент “Ага!” Марії

Коли її племінник показав їй криві навчання для моделі-зубрили, Марія нарешті зрозуміла:

“Отже, моя ‘ідеальна’ модель була хворою весь час! Криві показували симптоми — я просто не знала, як їх читати!”


🔧 План відновлення: Виправляємо недонавчання

Коли ваша модель занадто проста (як ледар), ось рецепт:

1. Збільшити складність моделі

from sklearn.tree import DecisionTreeRegressor

# Занадто проста — недонавчання
simple_tree = DecisionTreeRegressor(max_depth=2)

# Краще — схоплює більше патернів
deeper_tree = DecisionTreeRegressor(max_depth=6)

2. Додати більше ознак

Марія зрозуміла, що самої температури недостатньо:

# Оригінал: Тільки температура
X_simple = df[['temperature']]

# Покращено: Бізнес-контекст важливий!
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
df['is_holiday'] = df['date'].isin(holidays).astype(int)
df['temp_squared'] = df['temperature'] ** 2  # Схоплюємо криву!
df['marketing_active'] = df['ad_spend'] > 0

X_enhanced = df[['temperature', 'temp_squared', 'is_weekend',
                  'is_holiday', 'marketing_active']]

3. Використати потужніший алгоритм

# Проста: Лінійна регресія (може недонавчитися на складних патернах)
from sklearn.linear_model import LinearRegression

# Потужніша: Random Forest (обробляє нелінійні патерни)
from sklearn.ensemble import RandomForestRegressor

# Ще потужніша: Gradient Boosting
from sklearn.ensemble import GradientBoostingRegressor

4. Зменшити регуляризацію

Якщо ви вже використовуєте регуляризацію (розглянемо далі), можливо, ви занадто караєте модель:

from sklearn.linear_model import Ridge

# Занадто багато регуляризації → недонавчання
model = Ridge(alpha=1000)  # Перебір!

# Кращий баланс
model = Ridge(alpha=1)

🛡️ План відновлення: Виправляємо перенавчання

Коли ваша модель запам’ятовує замість того, щоб вчитися (оригінальна проблема Марії), ось лікування:

1. Отримати більше тренувальних даних

Найефективніше рішення, коли можливо. Більше прикладів допомагають моделі вивчати патерни замість запам’ятовування шуму.

# Якщо 500 зразків → модель запам'ятовує
# Спробуйте 2000+ зразків → модель вчиться патернам

2. Спростити модель

Пам’ятаєте Статтю 7? Використаймо глибину дерева як приклад:

Вплив глибини дерева на перенавчання / Tree Depth Impact on Overfitting

1 (Underfitted) 5 (Balanced) 20 (Overfitted)
📚
Training Accuracy
0.850
Test Accuracy
0.834
⚠️
Overfitting Gap
0.016
✅ Добре / Good
🌿
Tree Complexity
23
листків / leaves

Типові випадки / Common Cases:

from sklearn.tree import DecisionTreeRegressor

# Перенавчання: Занадто глибоке, запам'ятовує все
deep_tree = DecisionTreeRegressor(max_depth=20)

# Краще: Контрольована складність
controlled_tree = DecisionTreeRegressor(
    max_depth=5,           # Обмежуємо глибину
    min_samples_split=20,  # Потрібно 20+ зразків для розділення
    min_samples_leaf=10    # Потрібно 10+ зразків у кожному листку
)

3. Видалити шумні ознаки

Іноді менше — це краще. Марія випадково включила “order_id” як ознаку — повністю марний шум!

# Занадто багато ознак (деякі — шум)
X_noisy = df[['temperature', 'order_id', 'cashier_name', 'random_number']]

# Тільки чисті ознаки
from sklearn.feature_selection import SelectKBest, f_regression

selector = SelectKBest(f_regression, k=5)
X_clean = selector.fit_transform(X_all, y)

4. Регуляризація — Секретна зброя

Це найважливіша техніка. Занурюємось глибше.


💊 Регуляризація: Навчаємо модель дисципліни

Регуляризація додає штраф за складність, змушуючи вашу модель залишатися простою та здатною до узагальнення.

Уявіть це так: Студент-зубрило міг би отримати 100%, написавши 50 сторінок конспектів. Регуляризація каже: “Ти можеш написати тільки 1 сторінку. Зосередься на важливому!”

Інтерактивна демонстрація: Побачте регуляризацію в дії

Regularization Effect / Ефект регуляризації

0.01 (Weak) 1.0 1000 (Strong)

Coefficients / Коефіцієнти

Performance / Продуктивність

📊 Train Error 0.05
🧪 Test Error 0.18
🔢 Non-zero Coefs 10/10
📏 Gap 0.13

Три типи регуляризації

L2 Регуляризація (Ridge) — Метод “Стиснення”

Штраф: Сума квадратів коефіцієнтів

Loss=MSE+αj=1nwj2\text{Loss} = \text{MSE} + \alpha \sum_{j=1}^{n} w_j^2

Ефект: Зменшує всі коефіцієнти до нуля, але рідко до точного нуля.

from sklearn.linear_model import Ridge

# alpha контролює силу регуляризації
ridge_light = Ridge(alpha=0.01)   # Мінімальне стиснення
ridge_medium = Ridge(alpha=1.0)   # Помірне
ridge_strong = Ridge(alpha=100)   # Сильне стиснення

Коли використовувати: Вибір за замовчуванням. Добре, коли всі ознаки можуть бути корисними.

L1 Регуляризація (Lasso) — “Селектор ознак”

Штраф: Сума абсолютних значень коефіцієнтів

Loss=MSE+αj=1nwj\text{Loss} = \text{MSE} + \alpha \sum_{j=1}^{n} |w_j|

Ефект: Зменшує ТА видаляє ознаки, встановлюючи коефіцієнти точно в нуль!

from sklearn.linear_model import Lasso

lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)

# Перевіряємо, які ознаки були видалені
print("🔍 Аналіз ознак:")
for name, coef in zip(feature_names, lasso.coef_):
    status = "✅ ЗАЛИШЕНО" if coef != 0 else "❌ ВИДАЛЕНО"
    print(f"  {name}: {coef:.4f} ({status})")

Коли використовувати: Коли ви підозрюєте, що багато ознак нерелевантні (як “order_id” Марії).

Elastic Net — Найкраще з обох світів

Штраф: Мікс L1 та L2

Loss=MSE+α1wj+α2wj2\text{Loss} = \text{MSE} + \alpha_1 \sum |w_j| + \alpha_2 \sum w_j^2
from sklearn.linear_model import ElasticNet

# l1_ratio: 0 = чистий Ridge, 1 = чистий Lasso, 0.5 = збалансований
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)

Коли використовувати: Коли хочете відбір ознак (L1) плюс стабільні групи (L2).

Результати регуляризації Марії

print("\n🍕 Порівняння регуляризації для моделі Марії")
print("=" * 70)
print(f"{'Метод':<15} {'Трен. RMSE':>12} {'Тест. RMSE':>12} {'Розрив':>10} {'Ознак':>10}")
print("-" * 70)

# Результати після застосування кожного методу
# Без рег.        2.1          48.3       46.2         20
# Ridge           8.2          10.1        1.9         20
# Lasso           8.9           9.8        0.9         12  ← 8 марних ознак видалено!
# Elastic         8.5           9.9        1.4         15

Реакція Марії: “Lasso видалив 8 ознак, які я вважала важливими — включаючи день встановлення печі! Тепер моя модель насправді працює!”


✅ Крос-валідація: Довіряй, але перевіряй

Не покладайтеся на один розподіл train/test. Використовуйте крос-валідацію для надійної оцінки:

from sklearn.model_selection import cross_val_score

def evaluate_model(model, X, y):
    """5-fold крос-валідація для надійних оцінок."""
    scores = cross_val_score(
        model, X, y,
        cv=5,  # 5 різних розподілів train/test
        scoring='neg_mean_squared_error'
    )
    rmse_scores = np.sqrt(-scores)
    return rmse_scores.mean(), rmse_scores.std()

# Порівнюємо моделі надійно
models = [
    ("Проста (depth=2)", DecisionTreeRegressor(max_depth=2)),
    ("Збалансована (depth=5)", DecisionTreeRegressor(max_depth=5)),
    ("Глибока (depth=15)", DecisionTreeRegressor(max_depth=15)),
]

print("\n📊 Результати крос-валідації (5-fold)")
print("=" * 55)
for name, model in models:
    mean_rmse, std_rmse = evaluate_model(model, X, y)
    print(f"{name}: {mean_rmse:.1f} ± {std_rmse:.1f} піц")

Результат:

📊 Результати крос-валідації (5-fold)
=======================================================
Проста (depth=2): 12.5 ± 1.2 піц
Збалансована (depth=5): 8.7 ± 0.9 піц   ← Переможець!
Глибока (depth=15): 15.2 ± 3.5 піц

Збалансована модель виграє — найнижча помилка І найнижча варіативність (число ±).


🎯 Повне відновлення: Нова модель Марії

Давайте зберемо все разом для піцерії Марії:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error

# Очищений датасет Марії
np.random.seed(42)
n = 500

data = {
    'temperature': np.random.uniform(5, 35, n),
    'is_weekend': np.random.choice([0, 1], n, p=[0.7, 0.3]),
    'is_holiday': np.random.choice([0, 1], n, p=[0.95, 0.05]),
    'hour': np.random.choice(range(10, 23), n),
    'marketing_active': np.random.choice([0, 1], n, p=[0.8, 0.2]),
}

df = pd.DataFrame(data)
df['is_peak'] = ((df['hour'] >= 18) & (df['hour'] <= 21)).astype(int)

# Справжня залежність (що відкрила Марія)
df['sales'] = (
    80 +                                    # Базовий попит
    -0.8 * df['temperature'] +              # Холод = більше замовлень
    25 * df['is_weekend'] +                 # Вихідні +25
    45 * df['is_holiday'] +                 # Свята +45
    15 * df['is_peak'] +                    # Вечірній пік +15
    10 * df['marketing_active'] +           # Маркетинг +10
    np.random.normal(0, 12, n)              # Реальний шум
).clip(30, 200)

X = df[['temperature', 'is_weekend', 'is_holiday', 'hour', 'is_peak', 'marketing_active']]
y = df['sales']

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

# Порівняння: Стара модель vs Нова модель
models = {
    '❌ Стара (Зубрило)': DecisionTreeRegressor(max_depth=None),  # Без обмежень!
    '✅ Нова (Збалансована)': RandomForestRegressor(n_estimators=50, max_depth=5, random_state=42)
}

print("\n🍕 ТРАНСФОРМАЦІЯ МОДЕЛІ МАРІЇ")
print("=" * 75)
print(f"{'Модель':<28} {'Трен. RMSE':>12} {'Тест. RMSE':>12} {'Розрив':>10} {'Статус':>10}")
print("-" * 75)

for name, model in models.items():
    model.fit(X_train, y_train)

    train_rmse = np.sqrt(mean_squared_error(y_train, model.predict(X_train)))
    test_rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
    gap = test_rmse - train_rmse

    status = "ХВОРА" if gap > 10 else "ЗДОРОВА"
    print(f"{name:<28} {train_rmse:>10.1f} {test_rmse:>12.1f} {gap:>10.1f}   {status}")

Результат:

🍕 ТРАНСФОРМАЦІЯ МОДЕЛІ МАРІЇ
===========================================================================
Модель                        Трен. RMSE   Тест. RMSE     Розрив     Статус
---------------------------------------------------------------------------
❌ Стара (Зубрило)                 2.1         28.4        26.3      ХВОРА
✅ Нова (Збалансована)             9.8         11.2         1.4    ЗДОРОВА

Вплив на бізнес

До (Модель-зубрило):

  • Тестова помилка: ±28 піц
  • Катастрофа понеділка: 68 піц витрачено
  • Тижневі збитки: ~$1,200

Після (Збалансована модель):

  • Тестова помилка: ±11 піц
  • Тепер Марія замовляє в межах ±15 піц від реального попиту
  • Тижневі збитки: ~$180

Річна економія: $53,000 💰


🚨 Поширені пастки, яких слід уникати

Пастка 1: Налаштування на тестових даних

Неправильно:

# Тестування різних alpha на ТЕСТОВОМУ наборі ❌
for alpha in [0.01, 0.1, 1, 10, 100]:
    model = Ridge(alpha=alpha)
    model.fit(X_train, y_train)
    test_score = model.score(X_test, y_test)  # Підглядаємо!
    print(f"Alpha {alpha}: {test_score}")
# Тепер ваш тестовий набір забруднений!

Правильно:

# Використовуємо крос-валідацію на ТРЕНУВАЛЬНОМУ наборі ✅
from sklearn.model_selection import GridSearchCV

param_grid = {'alpha': [0.01, 0.1, 1, 10, 100]}
grid_search = GridSearchCV(Ridge(), param_grid, cv=5)
grid_search.fit(X_train, y_train)  # Тільки тренувальні дані!

print(f"Найкращий alpha: {grid_search.best_params_['alpha']}")
# Тестовий набір залишається недоторканим для фінальної оцінки

Пастка 2: Ігнорування розриву

Неправильно: “Моя модель має 98% точності на тренуванні — запускаємо!”

Правильно: Завжди порівнюйте train vs test. Розрив розповідає історію.

Пастка 3: Надмірна регуляризація

Неправильно: Встановлення alpha=10000, бо “регуляризація — це добре”

Правильно: Налаштуйте alpha для оптимального балансу. Занадто багато регуляризації спричиняє недонавчання!


🎓 Ключові висновки

Підсумуймо, що Марія (і ви) дізналися сьогодні:

1. Перенавчання = Запам’ятовування, а не навчання

  • Ознаки: Дуже низька трен. помилка, висока тест. помилка, великий розрив
  • Виправлення: Спростити модель, регуляризувати, отримати більше даних

2. Недонавчання = Занадто просто, щоб вчитися

  • Ознаки: Висока трен. помилка, висока тест. помилка, малий розрив
  • Виправлення: Збільшити складність, додати ознаки, зменшити регуляризацію

3. Компроміс зміщення-дисперсії є фундаментальним

  • Високе зміщення → недонавчання
  • Висока дисперсія → перенавчання
  • Мета: Знайти золоту середину, де загальна помилка мінімальна

4. Регуляризація — ваш друг

  • L2 (Ridge): Зменшує коефіцієнти
  • L1 (Lasso): Зменшує + видаляє ознаки
  • Elastic Net: Найкраще з обох світів

5. Завжди валідуйте правильно

  • Використовуйте крос-валідацію для вибору моделі
  • Зберігайте тестовий набір тільки для фінальної оцінки
  • Слідкуйте за розривом train-test!

🚀 Що далі?

Збалансована модель Марії працює добре, але її племінник має ідею:

“Тітко, а що якби замість ОДНОГО дерева ми натренували СОТНІ дерев і дали їм голосувати за прогноз?”

“Сотні? Хіба це не призведе до ще більшого перенавчання?”

“В тому й краса — це насправді зменшує перенавчання! Це називається Random Forest.”

У Статті 9: Ансамблеві методи ви дізнаєтеся, як об’єднання багатьох слабких моделей створює сильний, надійний предиктор. Піцерія Марії стане ще розумнішою! 🌲🌲🌲


🛠️ Практичне завдання

Побудуйте предиктор цін на нерухомість, який уникає перенавчання:

Ознаки датасету:

  • sqft — площа (500-5000)
  • bedrooms — кількість спалень (1-6)
  • age — вік будинку в роках (0-50)
  • location_score — рейтинг району (1-10)
  • has_pool — басейн (0/1)

Ваша місія:

  1. Розділіть дані 80/20 train/test
  2. Натренуйте глибоке дерево рішень (без обмежень) — спостерігайте перенавчання
  3. Натренуйте Ridge регресію — перевірте, чи є недонавчання
  4. Налаштуйте Random Forest за допомогою крос-валідації
  5. Порівняйте фінальну продуктивність на тесті

Критерії успіху:

  • Тестовий RMSE в межах 15% від тренувального RMSE (здоровий розрив)
  • Визначте, яка модель має найкращий баланс зміщення-дисперсії

Сьогодні ви навчилися діагностувати та виправляти перенавчання. Тепер ідіть рятувати піци! 🍕

Успіхів! 🎯