W3docs

Полиномиальная регрессия

Узнайте, как работает полиномиальная регрессия, когда её применять и как построить, оценить и настроить модель в Python с помощью scikit-learn.

Линейная регрессия проводит прямую линию через данные. Когда зависимость между входными и выходными значениями криволинейная — например, траектория мяча, рост бактериальной колонии или кривая доза-реакция — прямая линия плохо аппроксимирует данные, сколько бы признаков вы ни добавляли. Полиномиальная регрессия решает эту проблему, добавляя степенные версии существующих признаков (, , …), что позволяет модели изгибаться в соответствии с кривизной данных, при этом по-прежнему используя метод наименьших квадратов под капотом.

На этой странице рассматривается:

  • Как полиномиальная регрессия математически расширяет линейную регрессию
  • Когда использовать полиномиальную регрессию и какую степень выбирать
  • Полный конвейер scikit-learn: преобразование признаков, обучение, оценка
  • Ловушка переобучения и способы её обнаружения с помощью кривых обучения/теста
  • Сравнение качества моделей с помощью RMSE и R²

Как работает полиномиальная регрессия

Уравнение

Линейная регрессия строит модель:

y = β₀ + β₁x

Полиномиальная регрессия степени n строит:

y = β₀ + β₁x + β₂x² + β₃x³ + … + βₙxⁿ

Ключевая идея состоит в том, что , и так далее рассматриваются как дополнительные признаки. Модель по-прежнему линейна по коэффициентам (значениям β); нелинейными являются только признаки. Это означает, что метод наименьших квадратов по-прежнему работает — нужно лишь предварительно обработать входную матрицу.

Степени и их смысл

СтепеньНазваниеФорма
1ЛинейнаяПрямая линия
2КвадратичнаяОдна кривая (парабола)
3КубическаяОдна точка перегиба
4+Высшего порядкаБолее сложные кривые

Выбор правильной степени — ключевой навык. Слишком низкая степень ведёт к недообучению (модель не улавливает реальную кривизну). Слишком высокая — к переобучению (модель запоминает шум и плохо работает на новых данных).

Когда использовать полиномиальную регрессию

Используйте полиномиальную регрессию, когда:

  • На точечной диаграмме видна чёткая кривая в данных, которую прямая линия не может уловить
  • Вам нужна быстрая и интерпретируемая альтернатива древовидным моделям при умеренной кривизне
  • У вас уже есть работающая базовая линейная регрессия, но она недообучается

Предпочтите другие подходы, когда:

  • Зависимость очень сложная или имеет много признаков → попробуйте деревья решений или градиентный бустинг
  • Нужно предсказывать далеко за пределами диапазона обучения (экстраполяция) → полиномы высокой степени резко расходятся за пределами диапазона данных
  • У вас много входных признаков → каждый признак получает n полиномиальных членов, поэтому матрица признаков быстро растёт

Построение модели полиномиальной регрессии

Шаг 1: Импорт библиотек

import numpy as np
import matplotlib
matplotlib.use('Agg')  # non-interactive backend for scripts
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

Трансформер PolynomialFeatures из scikit-learn автоматически выполняет расширение x → [1, x, x², …, xⁿ]. Объединение его с LinearRegression в Pipeline делает код чистым и предотвращает утечку данных при кросс-валидации.

Шаг 2: Создание тестовых данных

rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, 80).reshape(-1, 1)   # 80 points from -3 to 3
y = 0.5 * X.ravel()**2 - X.ravel() + 2 + rng.normal(0, 0.5, 80)

# True relationship: y ≈ 0.5x² - x + 2  (a parabola with noise)

Базовая зависимость является квадратичной, поэтому полином степени 2 должен хорошо её восстановить. Именно в таких ситуациях линейная регрессия систематически недообучается.

Шаг 3: Разделение на обучающую и тестовую выборки

Всегда разделяйте данные перед обучением, чтобы иметь отложенные данные для оценки. Подробное объяснение см. в разделе Train/Test Split.

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

print(f"Training samples: {len(X_train)}")  # 64
print(f"Test samples:     {len(X_test)}")   # 16

Шаг 4: Построение и обучение конвейера

degree = 2
model = make_pipeline(
    PolynomialFeatures(degree=degree, include_bias=False),
    LinearRegression()
)
model.fit(X_train, y_train)

make_pipeline соединяет два шага в цепочку: PolynomialFeatures преобразует входные данные, затем LinearRegression обучается на расширенных признаках. Вы вызываете .fit(), .predict() и .score() на конвейере точно так же, как на обычном оценщике.

Шаг 5: Оценка модели

y_pred_train = model.predict(X_train)
y_pred_test  = model.predict(X_test)

rmse_train = np.sqrt(mean_squared_error(y_train, y_pred_train))
rmse_test  = np.sqrt(mean_squared_error(y_test,  y_pred_test))
r2_train   = r2_score(y_train, y_pred_train)
r2_test    = r2_score(y_test,  y_pred_test)

print(f"Train RMSE: {rmse_train:.4f}   Test RMSE: {rmse_test:.4f}")
print(f"Train R²:   {r2_train:.4f}   Test R²:   {r2_test:.4f}")

Ожидаемый вывод:

Train RMSE: 0.4605   Test RMSE: 0.4538
Train R²:   0.9480   Test R²:   0.9514

Значение R² около 0,95 на отложенных данных означает, что модель объясняет около 95% дисперсии — отличный результат для таких зашумлённых данных. Близкие значения на обучающей и тестовой выборках говорят о том, что модель обобщает данные, а не переобучается.

Интерпретация метрик:

  • RMSE (корень из среднеквадратичной ошибки) — средняя ошибка в тех же единицах, что и y. Чем меньше, тем лучше.
  • R² (коэффициент детерминации) — доля объяснённой дисперсии. Значения ближе к 1,0 лучше; 0 означает, что модель не лучше предсказания среднего значения.

Шаг 6: Визуализация подгонки

X_line = np.linspace(-3.5, 3.5, 200).reshape(-1, 1)
y_line = model.predict(X_line)

plt.figure(figsize=(7, 5))
plt.scatter(X_train, y_train, alpha=0.6, label='Train', color='steelblue', s=25)
plt.scatter(X_test,  y_test,  alpha=0.8, label='Test',  color='orange',    s=40, zorder=5)
plt.plot(X_line, y_line, color='red', linewidth=2, label=f'Degree-{degree} fit')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Polynomial Regression (degree 2)')
plt.legend()
plt.tight_layout()
plt.savefig('poly_reg_fit.png', dpi=120)
print("Plot saved.")

Красная кривая должна плавно проходить через точки рассеяния — достаточно близко, чтобы уловить параболическую форму, но не змеиться через отдельные точки шума.

Ловушка переобучения

Главный риск полиномиальной регрессии — выбор слишком высокой степени. Полином степени 15 может идеально запомнить все 80 обучающих точек (RMSE ≈ 0), но будет дико осциллировать между ними и провалится на новых данных.

Стандартный диагностический инструмент — кривая валидации: отображение ошибок обучения и теста в зависимости от степени.

degrees = range(1, 12)
train_rmses, test_rmses = [], []

for d in degrees:
    m = make_pipeline(PolynomialFeatures(d, include_bias=False), LinearRegression())
    m.fit(X_train, y_train)
    train_rmses.append(np.sqrt(mean_squared_error(y_train, m.predict(X_train))))
    test_rmses.append(np.sqrt(mean_squared_error(y_test,  m.predict(X_test))))

plt.figure(figsize=(7, 4))
plt.plot(degrees, train_rmses, 'o-', label='Train RMSE', color='steelblue')
plt.plot(degrees, test_rmses,  's-', label='Test RMSE',  color='orange')
plt.xlabel('Polynomial Degree')
plt.ylabel('RMSE')
plt.title('Validation Curve: Choosing the Best Degree')
plt.legend()
plt.tight_layout()
plt.savefig('poly_reg_validation_curve.png', dpi=120)
print("Plot saved.")

Что вы увидите:

  • Степень 1 (линейная): и обучающий, и тестовый RMSE высокие — недообучение.
  • Степень 2: оба резко падают — модель улавливает истинную форму.
  • Степень 5+: обучающий RMSE продолжает падать, но тестовый RMSE растёт — разрыв между обучающими и тестовыми значениями сигнализирует о переобучении.

Лучшая степень — та, при которой тестовый RMSE минимален до того, как начнёт снова расти. В этом примере это степень 2, что соответствует истинному процессу генерации данных.

Для более надёжного выбора степени используйте кросс-валидацию вместо единственного разделения на обучающую и тестовую выборки.

Использование numpy.polyfit (быстрая альтернатива)

Для простых одномерных задач функция polyfit из NumPy предлагает подгонку в одну строку без scikit-learn. Она удобна для исследовательского анализа, но не интегрируется с конвейерами и кросс-валидацией.

import numpy as np

x = np.array([1, 2, 3, 4, 5], dtype=float)
y = np.array([2.1, 4.0, 9.2, 16.1, 25.0])

# Fit a degree-2 polynomial
# Returns coefficients from highest to lowest degree: [a2, a1, a0]
coefficients = np.polyfit(x, y, 2)
poly = np.poly1d(coefficients)

print("Coefficients (highest to lowest degree):", np.round(coefficients, 3))
print("Prediction at x=6:", round(poly(6), 2))

Ожидаемый вывод:

Coefficients (highest to lowest degree): [ 1.121 -0.939  1.76 ]
Prediction at x=6: 36.5

Подогнанная кривая приближённо равна y ≈ 1.12x² - 0.94x + 1.76, что близко к истинной зависимости y = x² в этих данных. Коэффициенты не равны в точности [1, 0, 0], поскольку для подгонки имеется лишь пять зашумлённых точек.

np.poly1d оборачивает коэффициенты так, что вы можете вызывать полином как функцию: poly(6) вычисляет 1.121(36) - 0.939(6) + 1.76 ≈ 36.5.

Полный конвейер (все шаги вместе)

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# 1. Generate data (true relationship: y = 0.5x² - x + 2 + noise)
rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, 80).reshape(-1, 1)
y = 0.5 * X.ravel()**2 - X.ravel() + 2 + rng.normal(0, 0.5, 80)

# 2. Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 3. Build and fit
model = make_pipeline(PolynomialFeatures(degree=2, include_bias=False), LinearRegression())
model.fit(X_train, y_train)

# 4. Evaluate
rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
r2   = r2_score(y_test, model.predict(X_test))
print(f"Test RMSE: {rmse:.4f}")
print(f"Test R²:   {r2:.4f}")

# 5. Inspect learned coefficients
lr = model.named_steps['linearregression']
pf = model.named_steps['polynomialfeatures']
feature_names = pf.get_feature_names_out(['x'])
for name, coef in zip(feature_names, lr.coef_):
    print(f"  {name}: {coef:.4f}")
print(f"  intercept: {lr.intercept_:.4f}")

Ожидаемый вывод:

Test RMSE: 0.4538
Test R²:   0.9514
  x: -0.9625
  x^2: 0.4909
  intercept: 1.9654

Восстановленные коэффициенты (x^2 ≈ 0.49, x ≈ -0.96, перехват ≈ 1.97) близко совпадают с истинными значениями (0.5, -1, 2), что подтверждает: модель научилась правильной форме.

Масштабирование признаков и полиномиальная регрессия

При работе с полиномами высокой степени значения признаков быстро возрастают — x = 100 даёт x² = 10 000 и x³ = 1 000 000. Это может сделать задачу наименьших квадратов численно нестабильной.

Стандартное решение — масштабировать признаки с помощью StandardScaler внутри конвейера, до полиномиального расширения:

from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    PolynomialFeatures(degree=3, include_bias=False),
    LinearRegression()
)

Размещение StandardScaler первым означает, что он обучается только на обучающих данных и последовательно применяется к тестовым — без утечки данных. Подробнее о важности масштабирования см. в разделе Feature Scaling.

Распространённые ошибки

Пропуск разделения на обучающую и тестовую выборки. Если вы обучаете модель и оцениваете её на одних и тех же данных, полиномы высокой степени будут казаться идеальными, полностью проваливаясь на новых входных данных. Всегда оценивайте на отложенных данных.

Экстраполяция за пределы диапазона обучения. Полиномиальные кривые осциллируют и расходятся за пределами диапазона обучающих данных. Модель, обученная на x ∈ [0, 5], может давать абсурдные предсказания при x = 10. Линейная регрессия экстраполирует более консервативно.

Отсутствие масштабирования перед членами высокой степени. Большие значения признаков в сочетании с полиномиальным расширением высокой степени могут вызвать численное переполнение или плохо обусловленные матрицы. Используйте StandardScaler в конвейере.

Выбор степени только по ошибке на обучающей выборке. Обучающий RMSE всегда уменьшается при увеличении степени. Используйте тестовый RMSE или кросс-валидацию для нахождения степени, которая хорошо обобщается.

Забывать про include_bias=False. PolynomialFeatures по умолчанию добавляет константный столбец (член перехвата). LinearRegression тоже добавляет свой перехват. Передача include_bias=False в PolynomialFeatures позволяет избежать избыточного столбца.

Дальнейшие шаги

  • Линейная регрессия — основа прямой линии, на которой строится полиномиальная регрессия
  • Множественная регрессия — объединение множества признаков (полиномиальная регрессия, применённая к нескольким входным данным, также порождает члены взаимодействия)
  • Train/Test Split — правильный способ оценки любой регрессионной модели
  • Кросс-валидация — более надёжный способ настройки степени полинома, чем единственное разделение
  • Feature Scaling — почему и как стандартизировать входные данные перед обучением
Was this page helpful?