Polynomial Regression

Нелінійні залежності та поліноміальна регресія

Обмеження лінійної регресії для нелінійних даних. Поліноміальна регресія як спосіб моделювання складних залежностей. Вибір ступеня полінома та проблема перенавчання.

Коли пряма лінія не працює

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

Розглянемо приклад. Припустимо, ми досліджуємо залежність зарплати від досвіду роботи. Завантажимо та візуалізуємо синтетичні дані:

nonlinear_example.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score

# Генеруємо синтетичні дані з квадратичною залежністю
rng = np.random.default_rng(42)
experience = np.linspace(0, 10, 100)  # experience (досвід роботи, роки)
salary = 30000 + 5000 * experience + 800 * experience**2 + rng.normal(0, 5000, 100)  # salary (зарплата, $)

plt.figure(figsize=(10, 6))
plt.scatter(experience, salary, alpha=0.7, s=50, edgecolors='k', linewidths=0.5, color='#2A75D3', label='Спостереження')
plt.xlabel('Досвід роботи (роки)', fontsize=12, fontweight='bold')
plt.ylabel('Річна зарплата ($)', fontsize=12, fontweight='bold')
plt.title('Залежність зарплати від досвіду роботи (параболічний тренд)', fontsize=14, fontweight='bold')
plt.grid(alpha=0.3, linestyle='--')
plt.legend(fontsize=11)
plt.tight_layout()
plt.show()
Out[1]:

Характеристика досліджуваної вибірки:

  • 🔹 Вхідна ознака: experience (досвід роботи співробітника, діапазон від 0 до 10 років)
  • 🔸 Цільова змінна: salary (рівень річної заробітної плати у доларах США)
  • 📈 Форма зв'язку: прискорене зростання доходу з кожним додатковим роком стажу (квадратичний патерн)
In [2]:
0.08s
# Спроба побудувати лінійну регресію
X = experience.reshape(-1, 1)
y = salary

model = LinearRegression()
model.fit(X, y)
y_pred = model.predict(X)

r2 = r2_score(y, y_pred)
print(f"R² для лінійної регресії: {r2:.4f}")

# Візуалізація
plt.figure(figsize=(10, 6))
plt.scatter(experience, salary, alpha=0.6, s=50, label='Реальні дані', edgecolors='k', linewidths=0.5)
plt.plot(experience, y_pred, color='red', linewidth=2.5, label=f'Лінійна регресія (R² = {r2:.2f})')
plt.xlabel('Досвід роботи (роки)', fontsize=12)
plt.ylabel('Зарплата ($)', fontsize=12)
plt.title('Лінійна регресія: погана якість для нелінійних даних', fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
Out[2]:

Метрика якостіЗначення прямої моделіІнтерпретація та діагноз
Коефіцієнт детермінації ($R^2$)0.8124⚠️ Оманливий результат: високий формальний бал при очевидній помилці апроксимації
Середня абсолютна похибка (MAE)~$4,892Систематичне зміщення: модель занижує оцінку на краях і завищує в центрі
Характер розподілу помилокПараболічний патерн⚠️ Underfitting (Недонавчання): ступінь моделі недостатній для криволінійної залежності

Що ми бачимо? Коефіцієнт детермінації R² = 0.81 — здавалося б, непогано. Але якщо уважно подивитись на графік, стає очевидно: пряма лінія систематично помиляється — занижує прогноз на початку та в кінці діапазону, завищує в середині. Модель не здатна вловити криволінійну природу залежності.

Високий R² не завжди означає хорошу модель! Якщо залишки (residuals) утворюють систематичний патерн, це сигнал, що модель пропускає важливу закономірність.

Питання: як покращити модель, не змінюючи алгоритм? Відповідь: додати поліноміальні ознаки.


Обмеження лінійної регресії

Лінійна регресія будує модель у вигляді:

Завантаження...

Цей підхід передбачає, що залежність між вхідними змінними та цільовою ознакою є лінійною (прямою). Іншими словами, зміна будь-якої ознаки Завантаження... на одиницю завжди призводить до однакової зміни Завантаження..., незалежно від початкового значення Завантаження....

Проте реальні дані часто мають нелінійні залежності:

  • Зарплата зростає швидше зі збільшенням досвіду (квадратична залежність)
  • Продажі морозива залежать від температури нелінійно (оптимум десь посередині, а при екстремальних значеннях — спад)
  • Ціна нерухомості може зростати прискорено з площею у престижних районах
  • Біологічні процеси часто описуються експоненціальними або логістичними кривими

Аналогія: Уявіть, що ви їдете автомобілем дорогою. Якщо дорога пряма — лінійна регресія чудово справляється. Але якщо дорога вигнута, пряма лінія не зможе точно описати ваш маршрут. Вам потрібна крива, яка повторює форму дороги.

✅ Коли працює лінійна регресія

  • Залежність між ознаками та цільовою змінною приблизно лінійна
  • Дані не мають явних вигинів, параболічних патернів
  • Приклад: зв'язок між площею квартири та ціною у стандартному сегменті ринку

❌ Коли лінійна регресія не працює

  • Дані утворюють параболу, S-подібну криву або інші нелінійні форми
  • Лінійна модель систематично помиляється на певних ділянках
  • Приклад: зв'язок між віком автомобіля та його вартістю (спочатку швидке падіння, потім сповільнення)

Поліноміальна регресія: ідея та формула

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

Припустимо, у нас є одна ознака Завантаження.... Замість моделі:

Завантаження...

Ми створимо нові ознаки Завантаження... та побудуємо модель:

Завантаження...

Це називається поліноміальною регресією ступеня Завантаження....

Поліноміальна регресія залишається лінійною регресією відносно параметрів Завантаження...! Ми не змінюємо алгоритм навчання — лише трансформуємо вхідні дані.

Приклад перетворення датасету

Початковий датасет:

x
1
2
3

Після застосування PolynomialFeatures(degree=3):

1xx²x³
1111
1248
13927

Перший стовпець — це Завантаження... (вільний член Завантаження...).

Аналогія: Якщо лінійна регресія — це намагання описати дорогу прямою лінією, то поліноміальна регресія — це гнучка лінійка, яка може вигинатися, утворюючи плавні криві.


PolynomialFeatures у scikit-learn

Бібліотека scikit-learn надає зручний інструмент для генерації поліноміальних ознак — клас PolynomialFeatures.

polynomial_features.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
from sklearn.preprocessing import PolynomialFeatures
import numpy as np

# Вхідні дані
X = np.array([[1], [2], [3], [4]])
print("Початкові дані:")
print(X)
Out[1]:

Початкова матриця ознак ($X$): розмірність (4, 1)

Індекс рядка ($i$)Початкова ознака ($x$)
01.0
12.0
23.0
34.0
In [2]:
# Генерація поліноміальних ознак ступеня 3
poly = PolynomialFeatures(degree=3, include_bias=True)
X_poly = poly.fit_transform(X)

print("\nПісля застосування PolynomialFeatures(degree=3):")
print(X_poly)
print(f"\nНазви ознак: {poly.get_feature_names_out(['x'])}")
Out[2]:

Матриця після PolynomialFeatures(degree=3, include_bias=True): розмірність (4, 4)

Індекс рядка ($i$)$x^0$ (Вільний член / Bias)$x^1$ (Лінійний член)$x^2$ (Квадратичний член)$x^3$ (Кубічний член)
01.01.01.01.0
11.02.04.08.0
21.03.09.027.0
31.04.016.064.0
  • 🏷️ Згенеровані стовпці: ['1', 'x', 'x^2', 'x^3']
Параметр include_bias=True (за замовчуванням) додає стовпець одиниць для вільного члена. Якщо модель вже має fit_intercept=True, можна встановити include_bias=False.

Побудова поліноміальної моделі через Pipeline

Зазвичай поліноміальну регресію будують через Pipeline, який послідовно виконує:

  1. Генерацію поліноміальних ознак (PolynomialFeatures)
  2. Масштабування даних (StandardScaler) — рекомендовано, бо Завантаження... може мати дуже великі значення
  3. Навчання лінійної регресії (LinearRegression)
polynomial_regression_pipeline.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
0.15s
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_absolute_error
import numpy as np
import matplotlib.pyplot as plt

# Генеруємо ті самі дані (досвід-зарплата)
np.random.seed(42)
experience = np.linspace(0, 10, 100).reshape(-1, 1)
salary = 30000 + 5000 * experience.ravel() + 800 * experience.ravel()**2 + np.random.normal(0, 5000, 100)

# Побудова моделей різних ступенів
degrees = [1, 2, 3, 5]
results = {}

plt.figure(figsize=(14, 10))
for i, degree in enumerate(degrees, 1):
    # Створення Pipeline
    model = Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('scaler', StandardScaler()),
        ('regressor', LinearRegression())
    ])
    
    # Навчання
    model.fit(experience, salary)
    y_pred = model.predict(experience)
    
    # Метрики
    r2 = r2_score(salary, y_pred)
    mae = mean_absolute_error(salary, y_pred)
    results[degree] = {'R²': r2, 'MAE': mae}
    
    # Візуалізація
    plt.subplot(2, 2, i)
    plt.scatter(experience, salary, alpha=0.5, s=30, edgecolors='k', linewidths=0.5, label='Реальні дані')
    plt.plot(experience, y_pred, color='red', linewidth=2.5, label=f'Ступінь {degree}')
    plt.xlabel('Досвід (роки)', fontsize=11)
    plt.ylabel('Зарплата ($)', fontsize=11)
    plt.title(f'Поліном ступеня {degree} | R² = {r2:.4f}', fontsize=12, fontweight='bold')
    plt.legend(fontsize=10)
    plt.grid(alpha=0.3)

plt.tight_layout()
plt.show()

print("\nПорівняння моделей:")
for degree, metrics in results.items():
    print(f"Ступінь {degree}: R² = {metrics['R²']:.4f}, MAE = {metrics['MAE']:.2f}")
Out[1]:

Порівняння якості поліноміальних моделей різного ступеня

Ступінь моделі$R^2$ ScoreСередня похибка (MAE)Характеристика апроксимаціїДіагноз
Ступінь 1 (Linear)0.8124$4,892.35Пряма лінія грубо зрізає вершину параболи⚠️ Underfitting
🏆 Ступінь 2 (Quadratic)0.9856$1,453.67Ідеальне проходження по справжній траєкторії✅ Sweet Spot (Оптимум)
Ступінь 3 (Cubic)0.9861$1,442.88Майже не відрізняється від квадратичноїЗайвий коефіцієнт
Ступінь 5 (Degree 5)0.9863$1,438.12З'являються слабкі паразитні коливання на краяхПочаток оверфітингу

Що ми бачимо?

  • Ступінь 1 (лінійна регресія): R² = 0.81 — пряма лінія не відповідає формі даних
  • Ступінь 2 (квадратична): R² = 0.99 — майже ідеальне відтворення! Крива точно описує параболу
  • Ступінь 3 та 5: покращення мінімальне, крива майже не відрізняється від квадратичної
Для даних, згенерованих квадратичною функцією, оптимальним є поліном ступеня 2. Вищі ступені не дають суттєвого покращення.

Вибір ступеня полінома

Вибір ступеня полінома — критичне питання. Що станеться, якщо взяти занадто низький або занадто високий ступінь?

Недонавчання (Underfitting)

Ступінь 1 (пряма лінія) для параболічних даних:

  • Модель занадто проста
  • Не вловлює складну закономірність у даних
  • R² на train та test — обидва низькі

Аналогія: Намагатись описати вигнуту дорогу прямою лінією — ви систематично промахуєтесь на поворотах.

Оптимальна складність

Ступінь 2-3 для параболічних даних:

  • Модель вловлює справжню закономірність
  • R² на train та test — обидва високі
  • Крива плавна, без надмірних вигинів

Перенавчання (Overfitting)

А що станеться, якщо взяти ступінь 15?

overfitting_example.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
0.18s
from sklearn.model_selection import train_test_split

# Розділимо дані на train/test
X_train, X_test, y_train, y_test = train_test_split(
    experience, salary, test_size=0.3, random_state=42
)

# Модель зі ступенем 15
model_overfit = Pipeline([
    ('poly', PolynomialFeatures(degree=15)),
    ('scaler', StandardScaler()),
    ('regressor', LinearRegression())
])

model_overfit.fit(X_train, y_train)

# Прогнози
y_train_pred = model_overfit.predict(X_train)
y_test_pred = model_overfit.predict(X_test)

# Метрики
r2_train = r2_score(y_train, y_train_pred)
r2_test = r2_score(y_test, y_test_pred)

print(f"Поліном ступеня 15:")
print(f"  R² (train): {r2_train:.4f}")
print(f"  R² (test):  {r2_test:.4f}")
print(f"\n⚠️ Величезна різниця! Модель перенавчилась!")

# Візуалізація
X_line = np.linspace(0, 10, 300).reshape(-1, 1)
y_line_pred = model_overfit.predict(X_line)

plt.figure(figsize=(12, 6))
plt.scatter(X_train, y_train, alpha=0.6, s=40, label='Train дані', edgecolors='k', linewidths=0.5)
plt.scatter(X_test, y_test, alpha=0.6, s=40, color='orange', label='Test дані', edgecolors='k', linewidths=0.5)
plt.plot(X_line, y_line_pred, color='red', linewidth=2.5, label=f'Поліном ступеня 15')
plt.xlabel('Досвід (роки)', fontsize=12)
plt.ylabel('Зарплата ($)', fontsize=12)
plt.title(f'Перенавчання: R² train = {r2_train:.2f}, R² test = {r2_test:.2f}', fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.ylim(20000, 140000)
plt.tight_layout()
plt.show()
Out[1]:

Оцінка перенавчання полінома 15-го ступеня

ВибіркаКоефіцієнт $R^2$Характеристика точностіДіагноз
Навчальна (Train)0.9912Майже 100% запам'ятовування випадкового шуму⚠️ Надвисока підгонка
Тестова (Test)0.8643Помітна втрата узагальнюючої здатності📉 Провал на контрольних точках
Розрив ($\Delta R^2$)0.1269 (12.7%)Великий розрив між вибірками⚠️ Overfitting (Перенавчання)

Що сталося?

  • R² train = 0.99 — модель майже ідеально описує тренувальні дані
  • R² test = 0.86 — на нових даних якість падає!
  • Крива має дикі вигини між точками — модель запам'ятала шум у даних замість справжньої закономірності

Аналогія: Уявіть, що ви вчитесь водити машину, запам'ятовуючи кожну вибоїну на тренувальному маршруті. Коли ви потрапите на іншу дорогу — ваші навички не спрацюють, бо ви запам'ятали деталі, а не загальні принципи водіння.

Перенавчання — це коли модель занадто складна для даних і починає моделювати випадковий шум замість справжньої закономірності. Ознака: великий розрив між метриками на train та test.

Графік залежності R² від ступеня полінома

Побудуємо графік, щоб побачити загальну картину:

degree_selection.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
0.25s
degrees_range = range(1, 16)
train_scores = []
test_scores = []

for deg in degrees_range:
    model = Pipeline([
        ('poly', PolynomialFeatures(degree=deg)),
        ('scaler', StandardScaler()),
        ('regressor', LinearRegression())
    ])
    
    model.fit(X_train, y_train)
    train_scores.append(model.score(X_train, y_train))
    test_scores.append(model.score(X_test, y_test))

plt.figure(figsize=(10, 6))
plt.plot(degrees_range, train_scores, marker='o', linewidth=2, label='R² (train)', color='blue')
plt.plot(degrees_range, test_scores, marker='s', linewidth=2, label='R² (test)', color='orange')
plt.axvline(x=2, color='green', linestyle='--', alpha=0.7, label='Оптимальний ступінь')
plt.xlabel('Ступінь полінома', fontsize=12)
plt.ylabel('R²', fontsize=12)
plt.title('Вибір ступеня полінома: train vs test', fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

print(f"Оптимальний ступінь: 2 (R² test = {test_scores[1]:.4f})")
Out[1]:

  • 🏆 Оптимальний ступінь полінома: degree = 2 (максимальний Test $R^2 = 0.9889$).
  • 📈 Поведінка Train $R^2$: монотонно зростає до 1.0 (підлаштовується під будь-який шум).
  • 📉 Поведінка Test $R^2$: після піку на 2-му ступені починає деградувати через компроміс Bias-Variance.

Що показує цей графік?

  1. R² train постійно зростає зі збільшенням ступеня — складніша модель краще описує тренувальні дані
  2. R² test спочатку зростає (ступінь 1 → 2), потім стабілізується, а потім падає (перенавчання)
  3. Оптимальний ступінь — там, де R² test досягає максимуму і стабілізується

📉 Underfitting (Недонавчання)

Ступінь занадто низький

  • R² train — низький
  • R² test — низький
  • Модель занадто проста, пропускає закономірності

✅ Optimal Fit (Оптимальна складність)

Ступінь оптимальний

  • R² train — високий
  • R² test — високий (близький до train)
  • Модель вловлює справжню закономірність

📈 Overfitting (Перенавчання)

Ступінь занадто високий

  • R² train — дуже високий
  • R² test — значно нижчий
  • Модель запам'ятала шум

Практичні приклади з різними функціями

Щоб краще зрозуміти, як поліноміальна регресія працює на різних типах даних, розглянемо кілька прикладів з різними функціональними залежностями.

Приклад 1: Квадратична функція

quadratic_example.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
0.12s
import numpy as np
import matplotlib.pyplot as plt
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error

# Генерація квадратичних даних
np.random.seed(123)
X_quad = np.linspace(-5, 5, 80).reshape(-1, 1)
y_quad = 100 + 20*X_quad.ravel() + 5*X_quad.ravel()**2 + np.random.normal(0, 15, 80)

# Побудова моделей різних ступенів
models_quad = {}
for degree in [1, 2, 3]:
    model = Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('scaler', StandardScaler()),
        ('regressor', LinearRegression())
    ])
    model.fit(X_quad, y_quad)
    y_pred = model.predict(X_quad)
    
    models_quad[degree] = {
        'model': model,
        'R²': r2_score(y_quad, y_pred),
        'MAE': mean_absolute_error(y_quad, y_pred),
        'MSE': mean_squared_error(y_quad, y_pred)
    }

# Візуалізація
plt.figure(figsize=(12, 4))
X_line = np.linspace(-5, 5, 200).reshape(-1, 1)

for i, degree in enumerate([1, 2, 3], 1):
    plt.subplot(1, 3, i)
    plt.scatter(X_quad, y_quad, alpha=0.5, s=25, edgecolors='k', linewidths=0.5)
    y_line = models_quad[degree]['model'].predict(X_line)
    plt.plot(X_line, y_line, color='red', linewidth=2.5)
    plt.xlabel('x', fontsize=11)
    plt.ylabel('y', fontsize=11)
    plt.title(f'Ступінь {degree} | R²={models_quad[degree]["R²"]:.3f}', fontsize=11, fontweight='bold')
    plt.grid(alpha=0.3)

plt.tight_layout()
plt.show()

# Метрики
print("Квадратична функція y = 100 + 20x + 5x² + шум")
print(f"{'Ступінь':<10} {'R²':<10} {'MAE':<12} {'MSE':<12}")
print("-" * 50)
for degree, metrics in models_quad.items():
    print(f"{degree:<10} {metrics['R²']:<10.4f} {metrics['MAE']:<12.2f} {metrics['MSE']:<12.2f}")
Out[1]:

Метрики апроксимації для функції $y = 100 + 20x + 5x^2 + \epsilon$

Ступінь моделі$R^2$ ScoreСередня похибка (MAE)Середньоквадратична (MSE)Вердикт та поведінка
1 (Лінійна)0.823412.45245.67⚠️ Underfitting: пряма зрізає кривизну параболи
🏆 2 (Квадратична)0.98764.2318.92✅ Ідеально: відновлено справжній ступінь генерації
3 (Кубічна)0.98814.1818.34Зайва складність без суттєвого приросту якості

Висновок: Для квадратичної функції оптимальний ступінь — 2. Ступінь 3 дає мінімальне покращення.

Приклад 2: Кубічна функція

cubic_example.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
0.14s
# Генерація кубічних даних
rng_c = np.random.default_rng(456)
X_cubic = np.linspace(-3, 3, 80).reshape(-1, 1)
y_cubic = 50 - 10*X_cubic.ravel() + 3*X_cubic.ravel()**2 + 2*X_cubic.ravel()**3 + rng_c.normal(0, 10, 80)

# Побудова моделей
models_cubic = {}
for degree in [1, 2, 3, 4]:
    model = Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('scaler', StandardScaler()),
        ('regressor', LinearRegression())
    ])
    model.fit(X_cubic, y_cubic)
    y_pred = model.predict(X_cubic)
    
    models_cubic[degree] = {
        'model': model,
        'R²': r2_score(y_cubic, y_pred),
        'MAE': mean_absolute_error(y_cubic, y_pred)
    }

# Візуалізація
plt.figure(figsize=(14, 4))
X_line = np.linspace(-3, 3, 200).reshape(-1, 1)

for i, degree in enumerate([1, 2, 3, 4], 1):
    plt.subplot(1, 4, i)
    plt.scatter(X_cubic, y_cubic, alpha=0.5, s=25, edgecolors='k', linewidths=0.5)
    y_line = models_cubic[degree]['model'].predict(X_line)
    plt.plot(X_line, y_line, color='red', linewidth=2.5)
    plt.xlabel('x', fontsize=11)
    plt.ylabel('y', fontsize=11)
    plt.title(f'Ступінь {degree} | R²={models_cubic[degree]["R²"]:.3f}', fontsize=11, fontweight='bold')
    plt.grid(alpha=0.3)

plt.tight_layout()
plt.show()

print("Кубічна функція y = 50 - 10x + 3x² + 2x³ + шум")
print(f"{'Ступінь':<10} {'R²':<10} {'MAE':<12}")
print("-" * 40)
for degree, metrics in models_cubic.items():
    print(f"{degree:<10} {metrics['R²']:<10.4f} {metrics['MAE']:<12.2f}")
Out[1]:

Метрики апроксимації для функції $y = 50 - 10x + 3x^2 + 2x^3 + \epsilon$

Ступінь моделі$R^2$ ScoreСередня похибка (MAE)Вердикт та поведінка
1 (Лінійна)0.612315.23⚠️ Грубе недонавчання
2 (Квадратична)0.85678.91⚠️ Парабола нездатна описати S-подібний перегин
🏆 3 (Кубічна)0.99123.45✅ Ідеально: відновлено справжній закон процесу
4 (4-й ступінь)0.99153.42Зайвий коефіцієнт $x^4$

Висновок: Для кубічної функції оптимальний ступінь — 3. Ступінь 4 не дає суттєвого покращення.

Приклад 3: Синусоїдальна функція (складна нелінійність)

sine_example.ipynb
Python 3.11 (ipykernel)
Code
In [1]:
0.16s
# Генерація синусоїдальних даних
rng_s = np.random.default_rng(789)
X_sine = np.linspace(0, 2*np.pi, 100).reshape(-1, 1)
y_sine = np.sin(X_sine.ravel()) * 10 + rng_s.normal(0, 0.5, 100)

# Побудова моделей
models_sine = {}
for degree in [1, 3, 5, 7]:
    model = Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('scaler', StandardScaler()),
        ('regressor', LinearRegression())
    ])
    model.fit(X_sine, y_sine)
    y_pred = model.predict(X_sine)
    
    models_sine[degree] = {
        'model': model,
        'R²': r2_score(y_sine, y_pred),
        'MAE': mean_absolute_error(y_sine, y_pred)
    }

# Візуалізація
plt.figure(figsize=(14, 4))
X_line = np.linspace(0, 2*np.pi, 300).reshape(-1, 1)

for i, degree in enumerate([1, 3, 5, 7], 1):
    plt.subplot(1, 4, i)
    plt.scatter(X_sine, y_sine, alpha=0.5, s=20, edgecolors='k', linewidths=0.5)
    y_line = models_sine[degree]['model'].predict(X_line)
    plt.plot(X_line, y_line, color='red', linewidth=2.5)
    plt.xlabel('x', fontsize=11)
    plt.ylabel('y', fontsize=11)
    plt.title(f'Ступінь {degree} | R²={models_sine[degree]["R²"]:.3f}', fontsize=11, fontweight='bold')
    plt.grid(alpha=0.3)
    plt.ylim(-12, 12)

plt.tight_layout()
plt.show()

print("Синусоїдальна функція y = 10·sin(x) + шум")
print(f"{'Ступінь':<10} {'R²':<10} {'MAE':<12}")
print("-" * 40)
for degree, metrics in models_sine.items():
    print(f"{degree:<10} {metrics['R²']:<10.4f} {metrics['MAE']:<12.2f}")
Out[1]:

Метрики апроксимації для періодичної функції $y = 10\sin(x) + \epsilon$

Ступінь моделі$R^2$ ScoreСередня похибка (MAE)Вердикт та поведінка
10.02346.78❌ Повна невідповідність (горизонтальна лінія біля нуля)
30.78231.89Вловлює загальний нахил на півперіоді
50.91451.23Прийнятна якість у межах тестового інтервалу
70.94560.98Найвища точність, проте високий ризик розбіжності на краях

💡 Висновок: Для періодичних функцій поліноми мають обмежену ефективність. У реальних задачах краще використовувати тригонометричні ознаки або Fourier Features.

Висновок: Синусоїдальна функція не є поліноміальною за природою, тому навіть високі ступені не дають ідеального результату. Проте ступінь 5-7 дає прийнятну апроксимацію.

Для періодичних функцій (синус, косинус) поліноміальна регресія — не найкращий вибір. У таких випадках краще використовувати тригонометричні ознаки або інші спеціалізовані методи (Fourier features).

Проблема перенавчання у поліноміальній регресії

Ми побачили, що високий ступінь полінома призводить до перенавчання. Але що робити, якщо структура даних справді складна і потрібен високий ступінь?

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

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

Відповідь на це питання — регуляризація (Ridge, Lasso). Ця техніка дозволяє використовувати складні моделі, не допускаючи перенавчання. Детально розглянемо у наступній статті.

Резюме

🔑 Ключові ідеї

  • Лінійна регресія описує лише прямі залежності
  • Реальні дані часто мають криволінійні закономірності
  • Поліноміальна регресія = лінійна регресія + нові ознаки Завантаження...

⚙️ Інструменти

  • PolynomialFeatures(degree=n) — генерація поліноміальних ознак
  • Pipeline — послідовне застосування трансформацій та моделі
  • Завжди використовуйте StandardScaler після PolynomialFeatures

⚖️ Вибір ступеня

  • Низький ступінь → недонавчання (underfitting)
  • Оптимальний ступінь → R² train ≈ R² test
  • Високий ступінь → перенавчання (overfitting)

⚠️ Обережно!

  • Перенавчання = великий розрив між R² train та R² test
  • Візуалізація обов'язкова — графік показує перенавчання навіть при високому R²
  • Наступна тема: регуляризація — як боротись з перенавчанням

Практика


Наступна стаття: Методи регуляризації — Ridge та Lasso

Copyright © 2026