Практика — поліноміальна регресія та регуляризація на Housing Prices
Повернення до Housing Prices
У попередніх статтях модуля ми навчились працювати з датасетом Housing Prices — будували лінійну регресію, проводили EDA, навчились обробляці пропуски та масштабувати дані. У статті про практичне застосування лінійної регресії ми отримали R² ≈ 0.72 на тестових даних.
Питання: Чи можемо ми покращити цей результат, використовуючи поліноміальні ознаки та регуляризацію?
PolynomialFeatures(degree=2) до всіх ознак, ми отримаємо тисячі нових ознак! Це гарантовано призведе до катастрофічного перенавчання.Рішення: Вибрати підмножину найважливіших числових ознак для поліноміального перетворення.Аналогія: Уявіть, що ви готуєте страву з 80 інгредієнтів. Якщо кожен інгредієнт подвоїти, потроїти та змішати всі комбінації — вийде хаос. Натомість оберіть 5-7 ключових інгредієнтів і експериментуйте з ними — результат буде набагато кращим.
Експрес-аналіз даних (EDA)
Спочатку завантажимо дані та проведемо швидкий аналіз для вибору найважливіших ознак.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
# Завантаження датасету California Housing (вбудований у sklearn)
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing(as_frame=True)
df = data.frame
df['MedHouseVal'] = data.target
print(f"Розмір датасету: {df.shape}")
print(f"\nПерші 5 рядків:")
df.head()
Розмір вибірки: 20 640 спостережень, 9 ознак (8 числових факторів + 1 цільова змінна).
Перші 5 записів датасету:
| # | MedInc (дохід, $10k) | HouseAge (вік) | AveRooms (кімнати) | AveBedrms (спальні) | Population (люди) | AveOccup (мешканці) | Latitude (широта) | Longitude (довгота) | MedHouseVal (ціна, $100k) |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 8.3252 | 41.0 | 6.9841 | 1.0238 | 322.0 | 2.5556 | 37.88 | -122.23 | 4.526 |
| 1 | 8.3014 | 21.0 | 6.2381 | 0.9719 | 2401.0 | 2.1098 | 37.86 | -122.22 | 3.585 |
| 2 | 7.2574 | 52.0 | 8.2881 | 1.0734 | 496.0 | 2.8023 | 37.85 | -122.24 | 3.521 |
| 3 | 5.6431 | 52.0 | 5.8174 | 1.0731 | 558.0 | 2.5479 | 37.85 | -122.25 | 3.413 |
| 4 | 3.8462 | 52.0 | 6.2819 | 1.0811 | 565.0 | 2.1815 | 37.85 | -122.25 | 3.422 |
# Перевірка пропусків
print("Пропуски у датасеті:")
print(df.isnull().sum())
print("\n✅ Пропусків немає — California Housing чистий датасет")
| Ознака (Feature) | Опис змінного фактора | Кількість пропусків | Тип даних |
|---|---|---|---|
MedInc | Медіанний дохід жителів району ($10,000) | 0 (0.0%) | float64 |
HouseAge | Середній вік будинків у кварталі (роки) | 0 (0.0%) | float64 |
AveRooms | Середня кількість кімнат у житлі | 0 (0.0%) | float64 |
AveBedrms | Середня кількість спальних кімнат | 0 (0.0%) | float64 |
Population | Загальна кількість жителів кварталу | 0 (0.0%) | float64 |
AveOccup | Середня заселеність (осіб на домоволодіння) | 0 (0.0%) | float64 |
Latitude | Географічна широта району | 0 (0.0%) | float64 |
Longitude | Географічна довгота району | 0 (0.0%) | float64 |
MedHouseVal | Медіанна ціна житла ($100,000) — Target | 0 (0.0%) | float64 |
✅ Пропусків немає: Датасет California Housing є повністю заповненим і готовим до моделювання без потреби в додатковій імпутації (
SimpleImputer).
# Кореляція з цільовою змінною
correlations = df.corr()['MedHouseVal'].sort_values(ascending=False)
print("Кореляція ознак з MedHouseVal (ціна):\n")
print(correlations)
# Візуалізація
plt.figure(figsize=(10, 6))
correlations[1:].plot(kind='barh', color='steelblue', edgecolor='k')
plt.xlabel('Кореляція з ціною', fontsize=12)
plt.ylabel('Ознаки', fontsize=12)
plt.title('Кореляція ознак з медіанною ціною будинку', fontsize=14, fontweight='bold')
plt.axvline(0, color='black', linewidth=0.8, linestyle='--')
plt.grid(alpha=0.3, axis='x')
plt.tight_layout()
plt.show()
Кореляція ознак із цільовою змінною MedHouseVal (медіанна вартість будинку):
| Ознака | Опис змісту | Коефіцієнт кореляції Пірсона ($r$) | Зв'язок із ціною |
|---|---|---|---|
MedHouseVal | Медіанна вартість будинку | 1.0000 | Цільова змінна |
MedInc | Медіанний дохід жителів ($10k) | +0.6881 | 🔥 Дуже сильний позитивний зв'язок |
AveRooms | Середня кількість кімнат | +0.1519 | Помірний прямий вплив |
HouseAge | Вік будинку (роки) | +0.1056 | Слабкий позитивний вплив |
AveOccup | Середня заселеність оселі | -0.0237 | Майже нульовий зв'язок |
Population | Населення кварталу | -0.0246 | Практично відсутній зв'язок |
Longitude | Географічна довгота | -0.0460 | Слабкий просторовий градієнт |
AveBedrms | Середня кількість спалень | -0.0467 | Слабкий від'ємний зв'язок |
Latitude | Географічна широта | -0.1442 | Помітний географічний градієнт |

Що ми бачимо з аналізу кореляцій?
- MedInc (медіанний дохід жителів району, $10k) — безумовний лідер ($r = 0.69$). Багатші райони мають значно дорожчу нерухомість.
- AveRooms (середня кількість кімнат) — показує помірний прямий зв'язок ($r = 0.15$).
- HouseAge (вік будинку) — $r = 0.11$, старіші райони в Каліфорнії часто розташовані у престижніших прибережних зонах.
- Latitude (географічна широта) та Longitude (географічна довгота) — мають виразні нелінійні просторові ефекти (дорожче узбережжя проти дешевшої центральної долини).
- AveBedrms (кількість спалень), Population (населення) та AveOccup (заселеність) — мають дуже слабкий лінійний зв'язок.
MedInc (дохід), AveRooms (кімнати), HouseAge (вік), Latitude (широта) та Longitude (довгота). Це убезпечить нас від dimension explosion.Feature Engineering з поліноміальними ознаками
Оберемо топ-5 ознак для поліноміального перетворення: MedInc, AveRooms, HouseAge, Latitude, Longitude.
from sklearn.preprocessing import PolynomialFeatures
# Вибираємо підмножину ознак
selected_features = ['MedInc', 'AveRooms', 'HouseAge', 'Latitude', 'Longitude']
X_selected = df[selected_features]
y = df['MedHouseVal']
print(f"Початкова кількість ознак: {X_selected.shape[1]}")
# Застосовуємо PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X_selected)
print(f"Після PolynomialFeatures(degree=2): {X_poly.shape[1]} ознак")
print(f"\nНові ознаки включають:")
print(f" - Оригінальні: {X_selected.shape[1]}")
print(f" - Квадрати (x²): {X_selected.shape[1]}")
print(f" - Парні взаємодії (x_i * x_j): {X_selected.shape[1] * (X_selected.shape[1] - 1) // 2}")
# Назви нових ознак
feature_names = poly.get_feature_names_out(selected_features)
print(f"\nПриклади нових ознак:")
for name in feature_names[5:15]: # показуємо 10 прикладів
print(f" - {name}")
Розподіл згенерованих поліноміальних ознак (degree=2):
| Категорія ознак | Кількість | Приклади створених факторів |
|---|---|---|
| Оригінальні фактори ($x_i$) | 5 | MedInc, AveRooms, HouseAge, Latitude, Longitude |
| Квадратичні члени ($x_i^2$) | 5 | MedInc^2, AveRooms^2, HouseAge^2, Latitude^2, Longitude^2 |
| Парні взаємодії ($x_i \cdot x_j$) | 10 | MedInc·AveRooms, MedInc·Latitude, AveRooms·HouseAge тощо |
| Разом у матриці $X_{\text{poly}}$ | 20 | Контрольований розмір для уникнення перенавчання |
Приклади сформованих нелінійних взаємодій:
MedInc^2(нелінійне зростання вартості житла зі зростанням багатства)MedInc Latitude(вплив доходу залежно від географічної широти)AveRooms HouseAge(взаємодія просторості помешкання та віку забудови)
Результат: З 5 ознак ми отримали 20 ознак (включаючи взаємодії). Це керована кількість, яка не призведе до катастрофічного перенавчання.
Підготовка пайплайну
Побудуємо Pipeline, який послідовно виконує:
- Вибір ознак (через
ColumnTransformerабо вручну) - Масштабування (
StandardScaler) - Поліноміальні ознаки (
PolynomialFeatures) - Модель (LinearRegression / Ridge / Lasso)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
# Розділення на train/test
X = df[selected_features]
y = df['MedHouseVal']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Train set: {X_train.shape[0]} зразків")
print(f"Test set: {X_test.shape[0]} зразків")
# Базовий Pipeline (без поліноміальних ознак — для порівняння)
baseline_pipeline = Pipeline([
('scaler', StandardScaler()),
('regressor', LinearRegression())
])
baseline_pipeline.fit(X_train, y_train)
r2_train_baseline = baseline_pipeline.score(X_train, y_train)
r2_test_baseline = baseline_pipeline.score(X_test, y_test)
print(f"\n📊 Baseline (без поліноміальних ознак):")
print(f" R² train: {r2_train_baseline:.4f}")
print(f" R² test: {r2_test_baseline:.4f}")
Розподіл вибірки: 16 512 зразків у тренувальному наборі (80%), 4 128 зразків у тестовому (20%).
Метрики базової моделі (Baseline без поліноміальних ознак):
| Метрика | Train Set | Test Set | Різниця (Gap) | Оцінка |
|---|---|---|---|---|
| $R^2$ Score | 0.6012 | 0.5998 | 0.0014 | Базовий лінійний рівень точності |
💡 Точка відліку (Baseline): звичайна лінійна регресія на 5 сирих числових ознаках пояснює лише ~60% дисперсії ціни. Мета поліноміального моделювання — суттєво перевершити цей поріг.
Baseline результат: R² = 0.60 на тесті. Це наша точка відліку — модель без поліноміальних ознак.
Базовий експеримент — LinearRegression
Додамо поліноміальні ознаки до Pipeline та подивимось, що станеться.
# Pipeline з поліноміальними ознаками
poly_pipeline = Pipeline([
('poly', PolynomialFeatures(degree=2, include_bias=False)),
('scaler', StandardScaler()),
('regressor', LinearRegression())
])
poly_pipeline.fit(X_train, y_train)
# Оцінка
y_train_pred = poly_pipeline.predict(X_train)
y_test_pred = poly_pipeline.predict(X_test)
r2_train = r2_score(y_train, y_train_pred)
r2_test = r2_score(y_test, y_test_pred)
mae_test = mean_absolute_error(y_test, y_test_pred)
mse_test = mean_squared_error(y_test, y_test_pred)
print("📊 LinearRegression + PolynomialFeatures(degree=2):")
print(f" R² train: {r2_train:.4f}")
print(f" R² test: {r2_test:.4f}")
print(f" MAE test: {mae_test:.4f}")
print(f" RMSE test: {np.sqrt(mse_test):.4f}")
gap = r2_train - r2_test
print(f"\n⚠️ Gap (R² train - R² test): {gap:.4f}")
if gap > 0.05:
print("❌ Помітне перенавчання! Потрібна регуляризація.")
else:
print("✅ Перенавчання незначне.")
Результати тестування моделі LinearRegression + PolynomialFeatures(degree=2):
| Метрика якості | Train Set | Test Set | Різниця (Gap) | Діагностика перенавчання |
|---|---|---|---|---|
| $R^2$ Score | 0.6523 | 0.6412 | 0.0111 | ✅ Перенавчання помірне, якість зросла |
| MAE (середня абсолютна помилка) | — | 0.4823 ($48,230) | — | Точність покращилась відносно baseline |
| RMSE (корінь з MSE) | — | 0.6234 ($62,340) | — | Чутливість до великих відхилень |
✅ Покращення від нелінійності: Включення квадратів та парних взаємодій підняло $R^2$ на тесті з 0.60 до 0.6412 (+4.1% поясненої дисперсії), при цьому розрив train/test склав усього 0.0111.
Результат: Поліноміальні ознаки покращили R² test з 0.60 до 0.64! Перенавчання мінімальне (gap = 0.011). Це непоганий результат, але спробуємо покращити через регуляризацію.
Ridge регресія — боремося з перенавчанням
Застосуємо Ridge з різними значеннями alpha.
from sklearn.model_selection import GridSearchCV
# Підбір оптимального alpha через GridSearchCV
param_grid = {
'regressor__alpha': [0.1, 0.5, 1, 5, 10, 50, 100]
}
ridge_pipeline = Pipeline([
('poly', PolynomialFeatures(degree=2, include_bias=False)),
('scaler', StandardScaler()),
('regressor', Ridge())
])
grid_search = GridSearchCV(
ridge_pipeline,
param_grid,
cv=5,
scoring='r2',
n_jobs=-1
)
grid_search.fit(X_train, y_train)
print("🔍 Результати GridSearchCV для Ridge:")
print(f" Оптимальний alpha: {grid_search.best_params_['regressor__alpha']}")
print(f" R² (крос-валідація): {grid_search.best_score_:.4f}")
# Оцінка на test
best_ridge = grid_search.best_estimator_
r2_train_ridge = best_ridge.score(X_train, y_train)
r2_test_ridge = best_ridge.score(X_test, y_test)
y_test_pred_ridge = best_ridge.predict(X_test)
mae_ridge = mean_absolute_error(y_test, y_test_pred_ridge)
rmse_ridge = np.sqrt(mean_squared_error(y_test, y_test_pred_ridge))
print(f"\n📊 Ridge (alpha={grid_search.best_params_['regressor__alpha']}):")
print(f" R² train: {r2_train_ridge:.4f}")
print(f" R² test: {r2_test_ridge:.4f}")
print(f" MAE test: {mae_ridge:.4f}")
print(f" RMSE test: {rmse_ridge:.4f}")
print(f" Gap: {r2_train_ridge - r2_test_ridge:.4f}")
🔍 Результати оптимізації GridSearchCV для Ridge:
| Параметр / Метрика | Значення | Оцінка налаштування |
|---|---|---|
| Найкращий $\alpha$ (Alpha) | 10.0 | Помірна сила регуляризації |
| Середній $R^2$ (5-Fold CV) | 0.6445 | Стабільна оцінка на валідації |
Підсумкові метрики моделі Ridge ($\alpha=10$) на відкладеному тесті:
| Метрика | Train Set | Test Set | Різниця (Gap) | Статус |
|---|---|---|---|---|
| $R^2$ Score | 0.6501 | 0.6438 | 0.0063 | 🏆 Зменшення розриву в 1.8 раза |
| MAE | — | 0.4801 ($48,010) | — | Зниження середньої помилки |
| RMSE | — | 0.6209 ($62,090) | — | Найменша загальна помилка |
# Візуалізація залежності R² від alpha
cv_results = grid_search.cv_results_
alphas_tested = [0.1, 0.5, 1, 5, 10, 50, 100]
mean_scores = cv_results['mean_test_score']
plt.figure(figsize=(10, 6))
plt.plot(alphas_tested, mean_scores, marker='o', linewidth=2, markersize=8, color='steelblue')
plt.xscale('log')
plt.xlabel('Alpha (логарифмічна шкала)', fontsize=12)
plt.ylabel('R² (крос-валідація, 5-fold)', fontsize=12)
plt.title('Підбір оптимального alpha для Ridge', fontsize=14, fontweight='bold')
plt.axvline(x=10, color='red', linestyle='--', alpha=0.7, label='Оптимальний alpha=10')
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

Результат: Ridge з alpha=10 дає R² test = 0.644 та зменшує gap до 0.006 — модель стала стабільнішою!
Lasso регресія — автоматичний відбір ознак
Тепер спробуємо Lasso, щоб побачити, чи обнулить він якісь поліноміальні ознаки.
# Підбір alpha для Lasso
param_grid_lasso = {
'regressor__alpha': [0.001, 0.01, 0.05, 0.1, 0.5, 1]
}
lasso_pipeline = Pipeline([
('poly', PolynomialFeatures(degree=2, include_bias=False)),
('scaler', StandardScaler()),
('regressor', Lasso(max_iter=10000))
])
grid_search_lasso = GridSearchCV(
lasso_pipeline,
param_grid_lasso,
cv=5,
scoring='r2',
n_jobs=-1
)
grid_search_lasso.fit(X_train, y_train)
print("🔍 Результати GridSearchCV для Lasso:")
print(f" Оптимальний alpha: {grid_search_lasso.best_params_['regressor__alpha']}")
print(f" R² (крос-валідація): {grid_search_lasso.best_score_:.4f}")
# Оцінка на test
best_lasso = grid_search_lasso.best_estimator_
r2_train_lasso = best_lasso.score(X_train, y_train)
r2_test_lasso = best_lasso.score(X_test, y_test)
y_test_pred_lasso = best_lasso.predict(X_test)
mae_lasso = mean_absolute_error(y_test, y_test_pred_lasso)
rmse_lasso = np.sqrt(mean_squared_error(y_test, y_test_pred_lasso))
print(f"\n📊 Lasso (alpha={grid_search_lasso.best_params_['regressor__alpha']}):")
print(f" R² train: {r2_train_lasso:.4f}")
print(f" R² test: {r2_test_lasso:.4f}")
print(f" MAE test: {mae_lasso:.4f}")
print(f" RMSE test: {rmse_lasso:.4f}")
print(f" Gap: {r2_train_lasso - r2_test_lasso:.4f}")
# Скільки ознак обнулено?
lasso_regressor = best_lasso.named_steps['regressor']
n_nonzero = np.sum(np.abs(lasso_regressor.coef_) > 1e-5)
n_total = len(lasso_regressor.coef_)
print(f"\n🔍 Feature Selection:")
print(f" Всього поліноміальних ознак: {n_total}")
print(f" Відібрано Lasso: {n_nonzero}")
print(f" Обнулено: {n_total - n_nonzero}")
🔍 Результати оптимізації GridSearchCV для Lasso:
| Параметр / Метрика | Значення | Оцінка налаштування |
|---|---|---|
| Найкращий $\alpha$ (Alpha) | 0.01 | Делікатне розрідження матриці ваг |
| Середній $R^2$ (5-Fold CV) | 0.6441 | Практично паритет із Ridge |
Підсумкові метрики та відбір ознак Lasso ($\alpha=0.01$):
| Метрика | Train Set | Test Set | Різниця (Gap) | Відбір ознак |
|---|---|---|---|---|
| $R^2$ Score | 0.6498 | 0.6435 | 0.0063 | 🎯 Збережено 18 з 20 ознак |
| MAE | — | 0.4805 ($48,050) | — | Обнулено 2 зайві взаємодії |
| RMSE | — | 0.6212 ($62,120) | — | Висока точність і компактність |
🔍 Feature Selection: Lasso визначив, що 18 з 20 поліноміальних факторів несуть цінну інформацію, обнуливши лише 2 слабкі перехресні зв'язки.
Результат: Lasso дає результат майже ідентичний Ridge (R² = 0.644), але обнулив 2 ознаки з 20. Це означає, що більшість поліноміальних ознак корисні.
Порівняльна таблиця моделей
Зведемо всі результати в одну таблицю:
# Збір результатів
results_df = pd.DataFrame({
'Модель': [
'Baseline (без полінома)',
'LinearRegression + Poly',
'Ridge + Poly (α=10)',
'Lasso + Poly (α=0.01)'
],
'R² train': [
r2_train_baseline,
r2_train,
r2_train_ridge,
r2_train_lasso
],
'R² test': [
r2_test_baseline,
r2_test,
r2_test_ridge,
r2_test_lasso
],
'MAE test': [
mean_absolute_error(y_test, baseline_pipeline.predict(X_test)),
mae_test,
mae_ridge,
mae_lasso
],
'RMSE test': [
np.sqrt(mean_squared_error(y_test, baseline_pipeline.predict(X_test))),
np.sqrt(mse_test),
rmse_ridge,
rmse_lasso
],
'Gap': [
r2_train_baseline - r2_test_baseline,
gap,
r2_train_ridge - r2_test_ridge,
r2_train_lasso - r2_test_lasso
]
})
print("📊 Порівняльна таблиця моделей:\n")
print(results_df.to_string(index=False))
# Висновок
best_model_idx = results_df['R² test'].idxmax()
best_model_name = results_df.loc[best_model_idx, 'Модель']
best_r2 = results_df.loc[best_model_idx, 'R² test']
print(f"\n🏆 Найкраща модель: {best_model_name}")
print(f" R² test: {best_r2:.4f}")
print(f"\n✅ Покращення відносно Baseline: +{(best_r2 - r2_test_baseline):.4f} ({(best_r2 - r2_test_baseline) / r2_test_baseline * 100:.1f}%)")
📊 Зведена порівняльна таблиця результатів моделювання:
| Архітектура моделі | $R^2$ Train | $R^2$ Test | MAE Test ($) | RMSE Test ($) | Gap ($\Delta R^2$) | Підсумковий статус |
|---|---|---|---|---|---|---|
| Baseline (Linear без полінома) | 0.6012 | 0.5998 | 0.5234 ($52.3k) | 0.6589 ($65.9k) | 0.0014 | Базовий орієнтир |
| Linear + Poly(deg=2) | 0.6523 | 0.6412 | 0.4823 ($48.2k) | 0.6234 ($62.3k) | 0.0111 | Ріст точності (+4.1%) |
| Ridge + Poly ($\alpha=10$) | 0.6501 | 0.6438 | 0.4801 ($48.0k) | 0.6209 ($62.1k) | 0.0063 | 🏆 Найкраща модель |
| Lasso + Poly ($\alpha=0.01$) | 0.6498 | 0.6435 | 0.4805 ($48.1k) | 0.6212 ($62.1k) | 0.0063 | 🎯 Розріджена модель (18 ознак) |
🏆 Переможець експерименту — Ridge + Poly ($\alpha=10$):
- Досягнуто найвищого $R^2_{\text{test}} = 0.6438$ (+7.3% відносно Baseline).
- Мінімальна абсолютна помилка MAE: $48,010 (зменшення помилки прогнозу на $4,330 на кожному будинку).
- Розрив перенавчання (Gap) зменшено майже вдвічі: з 0.0111 до 0.0063.
Висновки:
- Поліноміальні ознаки покращили модель на 7.3%
- Ridge показав найкращий результат (R² = 0.644)
- Регуляризація зменшила gap майже втричі (з 0.011 до 0.006)
- Lasso обнулив лише 2 з 20 ознак — більшість поліноміальних взаємодій корисні
Візуалізація результатів
# График 1: Predicted vs Actual
y_test_pred_best = best_ridge.predict(X_test)
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
# 1. Predicted vs Actual
axes[0].scatter(y_test, y_test_pred_best, alpha=0.4, s=20, edgecolors='k', linewidths=0.3)
axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()],
'r--', linewidth=2, label='Ideal')
axes[0].set_xlabel('Справжня ціна', fontsize=11)
axes[0].set_ylabel('Передбачена ціна', fontsize=11)
axes[0].set_title('Predicted vs Actual (Ridge)', fontsize=12, fontweight='bold')
axes[0].legend(fontsize=10)
axes[0].grid(alpha=0.3)
# 2. Residuals
residuals = y_test - y_test_pred_best
axes[1].scatter(y_test_pred_best, residuals, alpha=0.4, s=20, edgecolors='k', linewidths=0.3)
axes[1].axhline(0, color='red', linestyle='--', linewidth=2)
axes[1].set_xlabel('Передбачена ціна', fontsize=11)
axes[1].set_ylabel('Залишки (residuals)', fontsize=11)
axes[1].set_title('Residual Plot', fontsize=12, fontweight='bold')
axes[1].grid(alpha=0.3)
# 3. Feature Importance (топ-10 коефіцієнтів)
ridge_regressor = best_ridge.named_steps['regressor']
poly_names = best_ridge.named_steps['poly'].get_feature_names_out(selected_features)
coefs = ridge_regressor.coef_
# Топ-10 за абсолютним значенням
coef_importance = pd.DataFrame({
'Feature': poly_names,
'Coefficient': coefs
}).sort_values('Coefficient', key=abs, ascending=False).head(10)
axes[2].barh(range(len(coef_importance)), coef_importance['Coefficient'],
color='steelblue', alpha=0.7, edgecolor='k')
axes[2].set_yticks(range(len(coef_importance)))
axes[2].set_yticklabels(coef_importance['Feature'], fontsize=9)
axes[2].set_xlabel('Коефіцієнт', fontsize=11)
axes[2].set_title('Топ-10 найважливіших ознак', fontsize=12, fontweight='bold')
axes[2].grid(alpha=0.3, axis='x')
axes[2].invert_yaxis()
plt.tight_layout()
plt.show()

Аналіз графіків:
- Predicted vs Actual: Точки розташовані близько до ідеальної лінії — модель добре передбачає ціни
- Residuals: Залишки розподілені навколо нуля без явного патерну — модель не має систематичних помилок
- Feature Importance: Найважливіша ознака —
MedInc(дохід),MedInc^2(квадрат доходу) також важлива
Інтерпретація моделі
Подивимось на найважливіші поліноміальні ознаки:
# Всі коефіцієнти
all_coefs = pd.DataFrame({
'Feature': poly_names,
'Coefficient': ridge_regressor.coef_
}).sort_values('Coefficient', key=abs, ascending=False)
print("📊 Топ-15 найважливіших поліноміальних ознак:\n")
print(all_coefs.head(15).to_string(index=False))
print("\n💡 Інтерпретація:")
print("1. MedInc (дохід) — найвпливовіша ознака (коефіцієнт > 0.5)")
print("2. MedInc^2 — квадратичний ефект доходу (нелінійне зростання ціни)")
print("3. Latitude, Longitude — географічне розташування критично важливе")
print("4. MedInc * Latitude — взаємодія доходу та широти (райони з високим доходом)")
print("5. AveRooms^2 — кількість кімнат має нелінійний вплив")
Рейтинг топ-15 найважливіших поліноміальних ознак у Ridge моделі:
| Ранг | Ознака (Feature) | Опис та інтерпретація впливу | Нормований коефіцієнт ($w_j$) | Вплив на ціну |
|---|---|---|---|---|
| 1 | MedInc | Медіанний дохід жителів району ($10k) | +0.5234 | 🟢 Головний позитивний драйвер |
| 2 | Latitude | Географічна широта (градієнт північ-південь) | -0.4123 | 🔴 Південні прибережні зони дорожчі |
| 3 | Longitude | Географічна довгота (градієнт захід-схід) | -0.3856 | 🔴 Узбережжя дорожче заглибини штату |
| 4 | MedInc^2 | Квадрат доходу (нелінійне супер-зростання) | +0.2934 | 🟢 Ефект елітної нерухомості |
| 5 | MedInc·Latitude | Взаємодія доходу та широти | -0.2567 | 🔵 Локалізовані анклави багатства |
| 6 | HouseAge | Середній вік будинків кварталу | +0.1823 | 🟢 Престижні історичні райони |
| 7 | AveRooms | Середня кількість кімнат | +0.1645 | 🟢 Загальний простір оселі |
| 8 | MedInc·Longitude | Взаємодія доходу та довготи | -0.1534 | 🔵 Ефект прибережного багатства |
| 9 | AveRooms^2 | Квадратичний член кімнат | +0.1245 | 🟢 Нелінійна премія за велику площу |
| 10 | Latitude^2 | Квадрат широти (кривина гео-зони) | -0.1123 | 🔵 Центри агломерацій (SF, LA) |
| 11 | Longitude^2 | Квадрат довготи | -0.0987 | 🔵 Просторовий вигин ціни |
| 12 | AveRooms·HouseAge | Простора старіша забудова | +0.0834 | 🟢 Великі історичні маєтки |
| 13 | MedInc·AveRooms | Дохід мешканців $ imes$ розмір житла | +0.0756 | 🟢 Синергія заможності та габаритів |
| 14 | HouseAge^2 | Квадрат віку будинків | +0.0689 | 🟢 Додаткова премія за зрілість |
| 15 | MedInc·HouseAge | Дохід $ imes$ вік житлового фонду | +0.0623 | 🟢 Зрілі престижні квартали |
💡 Ключові аналітичні інсайти:
- MedInc та його квадрат MedInc²: житло дорожчає не пропорційно, а з прискоренням у преміум-сегменті.
- Просторові координати (
Latitude,Longitude) та їхні взаємодії з доходом: локація залишається найсильнішим фактором після платоспроможності покупців.- Нелінійні ефекти
AveRooms: великі будинки додають вартості швидше, ніж просто лінійний перерахунок кімнат.
Бізнес-інтерпретація:
- Ціна зростає нелінійно з доходом (ознака
MedInc^2має позитивний коефіцієнт) - Географія надзвичайно важлива — навіть важливіша за квадрат доходу
- Взаємодія доходу та розташування (
MedInc * Latitude) підказує, що дорогі райони знаходяться на певних широтах
Резюме модуля 3.3
✅ Коли використовувати поліноміальну регресію?
- Дані мають нелінійні закономірності
- Візуалізація показує криволінійні залежності
- Лінійна модель має систематичні помилки
🔵 Ridge: коли?
- Багато ознак, більшість потенційно корисні
- Ознаки корельовані між собою
- Потрібна стабільна модель без різких обнулень
🟠 Lasso: коли?
- Багато ознак, більшість — шум
- Потрібен автоматичний feature selection
- Хочете інтерпретовану модель (менше ознак)
⚠️ Золоте правило
Завжди перевіряйте R² на test, а не тільки на train!
Високий R² train + низький R² test = перенавчання
Практичні завдання (3 рівні)
Завдання: Побудуйте поліноміальну модель на California Housing.
- Завантажте датасет
fetch_california_housing - Оберіть 3 найважливіші ознаки за кореляцією
- Побудуйте Pipeline з
PolynomialFeatures(degree=2),StandardScaler,LinearRegression - Порівняйте R² на train vs test
- Зробіть висновок про наявність перенавчання
Очікуваний результат:
- R² train та R² test близькі (різниця < 0.05)
- Покращення відносно лінійної моделі без полінома
Завдання: Використайте GridSearchCV для підбору оптимального alpha.
- Використайте California Housing з топ-5 ознаками
- Створіть Pipeline з
PolynomialFeatures(degree=2)таRidge - Налаштуйте
GridSearchCVз діапазоном alpha:[0.1, 1, 10, 100, 1000] - Знайдіть оптимальний alpha
- Побудуйте графік R² (CV) vs alpha
- Навчіть фінальну модель з оптимальним alpha та оцініть на test
Очікуваний результат:
- Графік показує оптимум у середній зоні
- Модель з оптимальним alpha має мінімальний gap між train та test
Завдання: Повний pipeline на реальному датасеті з Kaggle.
- Завантажте датасет House Prices - Advanced Regression Techniques з Kaggle
- Проведіть повний EDA:
- Аналіз пропусків (заповніть медіаною/модою)
- Кореляційний аналіз
- Виявлення та обробка викидів
- Створіть Pipeline з:
- Обробкою категоріальних ознак (
OneHotEncoderдля топ-5 категоріальних ознак) - Масштабуванням (
StandardScaler) - Поліноміальними ознаками (експериментуйте з degree=2 та 3)
- Обробкою категоріальних ознак (
- Порівняйте 4 моделі:
LinearRegression(baseline)LinearRegression + PolynomialRidge + PolynomialLasso + Polynomial
- Створіть звіт з:
- Таблицею метрик (R², MAE, RMSE, Gap)
- Графіками (predicted vs actual, residuals, feature importance)
- Висновками про найкращу модель
Додаткове завдання:
Спробуйте PolynomialFeatures лише для обраних ознак (не для всіх), щоб уникнути dimension explosion. Наприклад, застосуйте поліном лише до топ-7 числових ознак.
Очікуваний результат:
- Детальний звіт з аналізом
- Ridge або Lasso з поліноміальними ознаками дають найкращий результат
- Модель має R² test > 0.80 (якщо правильно обробили дані)
Кінець модуля 3.3. Наступний модуль: Дерева рішень та випадковий ліс (Decision Trees, Random Forest).
Регуляризація в регресії — Ridge та Lasso
Методи боротьби з перенавчанням через регуляризацію. L2-регуляризація (Ridge) та L1-регуляризація (Lasso). Вибір гіперпараметра alpha через крос-валідацію.
Дерева рішень — моделі з прозорою та зрозумілою логікою
Фундаментальний огляд дерев рішень (Decision Trees) для задач регресії та класифікації. Як працюють критерії розбиття (Entropy, Gini, MSE), прямокутні межі рішень, читання структури дерева та захист від перенавчання.