Линейная регрессия — один из базовых инструментов машинного обучения. Она проста, интерпретируема и эффективна, когда зависимость между признаками и целевой переменной близка к (внезапно!) линейной. Однако на практике данные часто демонстрируют более сложные, криволинейные взаимосвязи. Как нам их учитывать?
Один из самых простых способов — полиномиальная регрессия. Мы берем возможности линейной регрессии и расширяем их, позволяя модели "изгибаться" для лучшего соответствия данным. Делается это путем добавления степеней исходных признаков (`x²`, `x³`, ...) в модель. Несмотря на это, в основе своей она использует тот же математический аппарат, что и линейная регрессия, что делает её относительно простой для понимания и реализации.
y = β₀ + β₁x + ε
y = β₀ + β₁x + β₂x² + β₃x³ + ... + βₙxⁿ + ε
y = β₀ + β₁x₁ + β₂x₂ + β₃x₁² + β₄x₂² + β₅(x₁*x₂) + ε
[[ 2, 4, 8],
[ 3, 9, 27],
[ 4, 16, 64]]
[[a, b, a², a*b, b²],
[c, d, c², c*d, d²]]
y = β₀ + β₁x₁ + β₂x₂ + β₃x₁² + β₄x₂² + β₅(x₁*x₂) + ε
y = β₀ + β₁z₁ + β₂z₂ + β₃z₃ + β₄z₄ + β₅z₅ + ε
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures # <--- Ключевой инструмент!
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.pipeline import make_pipeline
data = sns.load_dataset('mpg')
display(data.head())
data.info()
print(f"\nКоличество пропусков в 'horsepower' до обработки: {data['horsepower'].isnull().sum()}")
data = data.dropna(subset=['horsepower'])
print(f"Количество пропусков в 'horsepower' после удаления: {data['horsepower'].isnull().sum()}")
# Количество пропусков в 'horsepower' до обработки: 6
# Количество пропусков в 'horsepower' после удаления: 0
X = data[['horsepower']]
y = data['mpg']
# Разделяем данные на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"\nРазмер обучающей выборки: {X_train.shape[0]} записей")
print(f"Размер тестовой выборки: {X_test.shape[0]} записей")
# Размер обучающей выборки: 313 записей
# Размер тестовой выборки: 79 записей
plt.figure(figsize=(10, 6))
sns.scatterplot(x=X_train['horsepower'], y=y_train, alpha=0.7, label='Обучающие данные')
sns.scatterplot(x=X_test['horsepower'], y=y_test, alpha=0.7, label='Тестовые данные', marker='^')
plt.title('Зависимость расхода топлива (MPG) от мощности (Horsepower)')
plt.xlabel('Мощность (Horsepower)')
plt.ylabel('Расход топлива (MPG)')
plt.legend()
plt.grid(True)
plt.show()
# Обучаем простую линейную регрессию
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
# Делаем предсказания на тестовой выборке
y_lin_pred_test = lin_reg.predict(X_test)
lin_r2 = r2_score(y_test, y_lin_pred_test)
print(f"Линейная регрессия (на тесте): R^2 = {lin_r2:.3f}")
# Линейная регрессия (на тесте): R^2 = 0.566
plt.figure(figsize=(10, 6))
plt.scatter(X['horsepower'], y, alpha=0.6, label='Все данные'
X_plot = np.linspace(X['horsepower'].min(), X['horsepower'].max(), 100).reshape(-1, 1)
y_lin_plot = lin_reg.predict(X_plot)
plt.plot(X_plot, y_lin_plot, color='red', linewidth=2, label=f'Линейная регрессия (R^2={lin_r2:.3f})')
plt.title('Линейная регрессия: MPG vs Horsepower')
plt.xlabel('Мощность (Horsepower)')
plt.ylabel('Расход топлива (MPG)')
plt.legend()
plt.grid(True)
plt.show()
degree = 2
poly_reg_pipeline = make_pipeline(
PolynomialFeatures(degree, include_bias=False), # include_bias=False, т.к. LinearRegression добавит свой
LinearRegression()
)
# Обучаем пайплайн на обучающих данных
poly_reg_pipeline.fit(X_train, y_train)
# Делаем предсказания на тестовой выборке
y_poly_pred_test = poly_reg_pipeline.predict(X_test)
# Оцениваем полиномиальную модель на тестовых данных
poly_r2 = r2_score(y_test, y_poly_pred_test)
print(f"\nПолиномиальная регрессия (degree={degree}, на тесте): R^2 = {poly_r2:.3f}")
# Полиномиальная регрессия (degree=2, на тесте): R^2 = 0.639
plt.figure(figsize=(12, 7))
plt.scatter(X['horsepower'], y, alpha=0.6, label='Все данные')
# Предсказания полиномиальной модели для диапазона X_plot
y_poly_plot = poly_reg_pipeline.predict(X_plot)
plt.plot(X_plot, y_poly_plot, color='green', linewidth=3, label=f'Полиномиальная регрессия (degree={degree}, R^2={poly_r2:.3f})')
# Можно добавить и линейную для сравнения
plt.plot(X_plot, y_lin_plot, color='red', linewidth=1, linestyle='--', label=f'Линейная регрессия (R^2={lin_r2:.3f})')
plt.title('Сравнение линейной и полиномиальной регрессии второй степени')
plt.xlabel('Мощность (Horsepower)')
plt.ylabel('Расход топлива (MPG)')
plt.legend()
plt.grid(True)
plt.show()
# Новое значение мощности, для которого хотим сделать прогноз
hp_new_value = 110
hp_new = pd.DataFrame({'horsepower': [hp_new_value]})
# Предсказание простой линейной моделью
mpg_pred_lin = lin_reg.predict(hp_new)
print(f"\nПрогноз MPG (линейная модель) для мощности {hp_new_value} л.с.: {mpg_pred_lin[0]:.2f}")
# Прогноз MPG (линейная модель) для мощности 110 л.с.: 22.72
# Предсказание полиномиальной моделью
mpg_pred_poly = poly_reg_pipeline.predict(hp_new)
print(f"Прогноз MPG (полиномиальная модель, degree={degree}) для мощности {hp_new_value} л.с.: {mpg_pred_poly[0]:.2f}")
# Прогноз MPG (полиномиальная модель, degree=2) для мощности 110 л.с.: 20.59
X_plot = np.linspace(X['horsepower'].min(), X['horsepower'].max(), 200).reshape(-1, 1)
X_plot_df = pd.DataFrame(X_plot, columns=['horsepower'])
degrees = [1, 2, 15] # Степени для сравнения: недообучение, баланс, переобучение
colors = ['red', 'green', 'purple']
labels = ["Степень 1 - недообучение",
"Степень 2 - баланс?",
"Степень 15 - явное переобучение"]
train_r2s = []
test_r2s = []
plt.figure(figsize=(14, 8))
plt.scatter(X_train['horsepower'], y_train, alpha=0.6, label='Обучающие данные')
plt.scatter(X_test['horsepower'], y_test, alpha=0.8, marker='^', label='Тестовые данные')
for i, degree in enumerate(degrees):
model = make_pipeline(
PolynomialFeatures(degree, include_bias=False),
LinearRegression()
)
model.fit(X_train, y_train)
# Предсказания для графика
y_plot_pred = model.predict(X_plot_df)
# Предсказания для оценки R^2
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
# Расчет R^2
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
train_r2s.append(train_r2)
test_r2s.append(test_r2)
# Рисуем кривую
plt.plot(X_plot, y_plot_pred, color=colors[i], linewidth=2.5,
label=f"{labels[i]} (Test R^2: {test_r2:.2f})")
plt.title('Недообучение, сбалансированная модель и переобучение (MPG vs Horsepower)')
plt.xlabel('Мощность (Horsepower)')
plt.ylabel('Расход топлива (MPG)')
plt.legend(loc='upper right')
plt.grid(True)
# Ограничим ось Y для наглядности, иначе кривая степени 15 может улететь
plt.ylim(data['mpg'].min() - 5, data['mpg'].max() + 10)
plt.show()
# Выведем R^2 для сравнения
print("\nСравнение R^2 для разных степеней:")
for i, degree in enumerate(degrees):
print(f"Степень {degree}: Train R^2 = {train_r2s[i]:.2f}, Test R^2 = {test_r2s[i]:.2f}")
# Сравнение R^2 для разных степеней:
# Степень 1: Train R^2 = 0.61, Test R^2 = 0.57
# Степень 2: Train R^2 = 0.70, Test R^2 = 0.64
# Степень 15: Train R^2 = 0.42, Test R^2 = 0.44
# --- Построение графика R² от степени полинома ---
degrees_to_test = range(1, 16) # Протестируем степени от 1 до 15
train_scores = []
test_scores = []
for degree in degrees_to_test:
model = make_pipeline(
PolynomialFeatures(degree, include_bias=False),
LinearRegression()
)
model.fit(X_train, y_train)
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
train_scores.append(r2_score(y_train, y_train_pred))
test_scores.append(r2_score(y_test, y_test_pred))
plt.figure(figsize=(10, 6))
plt.plot(degrees_to_test, train_scores, 'bo-', label='Train R²')
plt.plot(degrees_to_test, test_scores, 'ro-', label='Test R²')
plt.xlabel('Степень полинома')
plt.ylabel('R²')
plt.title('R² на обучающей и тестовой выборках vs степень полинома')
plt.legend()
plt.grid(True)
# Отметим максимум на тестовой кривой
best_degree_idx = np.argmax(test_scores)
best_degree = degrees_to_test[best_degree_idx]
plt.axvline(best_degree, linestyle='--', color='gray', label=f'Оптимальная степень ≈ {best_degree}')
plt.legend()
plt.ylim(min(train_scores + test_scores) - 0.1, 1.1) # Настроить масштаб оси Y
plt.xticks(degrees_to_test[::2]) # Показать не все тики на оси X
plt.show()
print(f"\nМаксимальный R² на тесте (R²={test_scores[best_degree_idx]:.2f}) достигается при степени {best_degree}")
# Максимальный R² на тесте (R²=0.65) достигается при степени 5