Множественная регрессия
Как работает множественная линейная регрессия: интерпретация коэффициентов, мультиколлинеарность и построение модели в Python с scikit-learn.
Множественная линейная регрессия расширяет простую линейную регрессию, используя две или более независимых переменных для предсказания непрерывного целевого значения. Вместо проведения прямой в двумерном пространстве модель подбирает гиперплоскость в стольких измерениях, сколько имеется признаков. Понимание того, как коэффициенты взаимодействуют — и когда они вводят в заблуждение — является ключевым навыком, которому посвящена эта страница.
Эта страница охватывает:
- Уравнение множественной регрессии и значение каждого коэффициента
- Как построить полный конвейер scikit-learn: загрузка, предобработка, обучение, оценка
- Почему важно масштабирование признаков и как выполнять его правильно
- Как интерпретировать и сравнивать масштабированные и немасштабированные коэффициенты
- Диагностика мультиколлинеарности — наиболее распространённая ловушка в множественной регрессии
- Анализ остатков для проверки допущений модели
- Когда выбирать множественную регрессию и что пробовать, если она не справляется
Уравнение множественной регрессии
Множественная линейная регрессия моделирует целевую переменную y как линейную комбинацию n входных признаков:
y = β₀ + β₁x₁ + β₂x₂ + ... + βₙxₙ + εβ₀— свободный член (intercept): предсказанное значениеy, когда все признаки равны нулюβ₁ … βₙ— коэффициенты: насколько изменяетсяyпри увеличении каждогоxᵢна одну единицу при постоянстве всех остальных признаковε— ошибка: та частьy, которую модель не может объяснить
Алгоритм находит коэффициенты, минимизируя сумму квадратов остатков (метод наименьших квадратов):
SSR = Σ(yᵢ - ŷᵢ)²Это задача имеет точное замкнутое аналитическое решение, поэтому LinearRegression из scikit-learn не требует итерационного градиентного спуска — обучение происходит практически мгновенно даже на наборах данных с сотнями тысяч строк.
Отличие от простой линейной регрессии
Простая линейная регрессия использует один признак. Множественная регрессия добавляет больше признаков, так что каждый коэффициент отражает частичный эффект этого признака — его влияние на целевую переменную при фиксации всех остальных признаков. Это более мощный подход, но он вводит новые риски, особенно мультиколлинеарность (см. Диагностика мультиколлинеарности).
Набор данных
В примерах ниже используется набор данных California Housing, встроенный в scikit-learn. Он содержит статистику жилья на уровне переписных блоков Калифорнии за 1990 год и включает 20 640 образцов с 8 признаками.
import pandas as pd
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()
df = pd.DataFrame(housing.data, columns=housing.feature_names)
df['MedHouseVal'] = housing.target # median house value in $100,000s
print(df.shape) # (20640, 9)
print(df.head())8 входных признаков:
| Признак | Описание |
|---|---|
MedInc | Медианный доход в блоке (в десятках тысяч долларов) |
HouseAge | Медианный возраст домов в блоке |
AveRooms | Среднее количество комнат на домохозяйство |
AveBedrms | Среднее количество спален на домохозяйство |
Population | Население блока |
AveOccup | Среднее количество жильцов на домохозяйство |
Latitude | Широта блока |
Longitude | Долгота блока |
Целевая переменная MedHouseVal — медианная стоимость дома в единицах $100 000, то есть значение 2.0 соответствует $200 000.
Построение модели шаг за шагом
Шаг 1 — Разделение данных
Всегда разделяйте данные до любой предобработки. Если подогнать масштабировщик на всём наборе данных, статистика тестовой выборки просочится в обучение, что даст излишне оптимистичную оценку. Подробное объяснение см. в разделе Разделение на обучающую и тестовую выборки.
from sklearn.model_selection import train_test_split
X = df[housing.feature_names]
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"Training samples: {len(X_train)}") # 16512
print(f"Test samples: {len(X_test)}") # 4128Шаг 2 — Масштабирование признаков
Коэффициенты множественной регрессии отражают единицы измерения каждого признака. MedInc измеряется в десятках тысяч долларов, а Population — это простое количество, которое может достигать 35 000. Без масштабирования коэффициент при Population будет очень маленьким не потому, что население не важно, а потому что его единица измерения мала.
StandardScaler преобразует каждый признак так, чтобы его среднее было равно нулю, а стандартное отклонение — единице, что делает величины коэффициентов непосредственно сопоставимыми. Подробнее о доступных масштабировщиках см. в разделе Масштабирование признаков.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit ONLY on training data
X_test_scaled = scaler.transform(X_test) # apply the same transformationКлючевое правило: вызывайте fit_transform на обучающих данных и transform (без подгонки) на тестовых. Подгонка на тестовых данных загрязнит оценку.
Шаг 3 — Обучение модели
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train_scaled, y_train)LinearRegression.fit() решает задачу МНК аналитически. Для классической множественной регрессии нет гиперпараметров для настройки — вы выбираете только набор признаков.
Шаг 4 — Оценка модели
import numpy as np
from sklearn.metrics import mean_squared_error, r2_score
y_pred = model.predict(X_test_scaled)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
print(f"Mean Squared Error: {mse:.4f}")
print(f"Root Mean Squared Error: {rmse:.4f} (±${rmse * 100_000:,.0f})")
print(f"R-squared: {r2:.4f}")Ожидаемый вывод:
Mean Squared Error: 0.5559
Root Mean Squared Error: 0.7456 (±$74,558)
R-squared: 0.5758Что означают метрики:
- MSE (средняя квадратичная ошибка) — среднее квадратов разностей между предсказаниями и реальными значениями. Возведение в квадрат штрафует большие ошибки сильнее, чем малые. Единицы измерения — квадрат единиц целевой переменной ($100 000²), поэтому прямая интерпретация затруднена.
- RMSE (среднеквадратичное отклонение) — квадратный корень из MSE, возвращающий результат в единицы целевой переменной. RMSE равный 0.75 означает, что предсказания модели в среднем отличаются примерно на $75 000.
- R² (коэффициент детерминации) — доля дисперсии целевой переменной, объясняемой моделью. R² = 0.58 означает, что модель объясняет 58% вариации цен на жильё. Значения варьируются от 0 (не лучше предсказания среднего) до 1 (идеальные предсказания). Отрицательный R² возможен, если модель хуже среднего — это явный признак проблемы.
R² ≈ 0.58 типичен для этого набора данных при использовании классической линейной регрессии. Связь между ценами на жильё и этими признаками частично нелинейна и включает географическую кластеризацию, которую гиперплоскость не может хорошо уловить. Алгоритмы на основе градиентного бустинга стабильно достигают 0.80+ на этом наборе данных.
Шаг 5 — Анализ коэффициентов
После масштабирования величины коэффициентов непосредственно сопоставимы — они показывают, какие признаки наиболее сильно влияют на предсказание:
coef_df = pd.DataFrame({
'Feature': housing.feature_names,
'Coefficient': model.coef_
}).sort_values('Coefficient', key=abs, ascending=False)
print(coef_df.to_string(index=False))
print(f"\nIntercept: {model.intercept_:.4f}")Ожидаемый вывод:
Feature Coefficient
Latitude -0.8969
Longitude -0.8698
MedInc 0.8544
AveBedrms 0.3393
AveRooms -0.2944
HouseAge 0.1225
AveOccup -0.0408
Population -0.0023
Intercept: 2.0719Чтение коэффициентов после стандартного масштабирования:
MedInc = 0.854— единственный наиболее сильный предиктор. Увеличение медианного дохода на одно стандартное отклонение предсказывает рост стоимости дома на $85 400 при прочих равных условиях.Latitude = -0.897иLongitude = -0.869— модель обнаружила, что более северные и более восточные переписные блоки, как правило, дешевле. Однако эти два географических признака сильно коррелированы (r = -0.93), что может дестабилизировать их индивидуальные коэффициенты (см. Диагностика мультиколлинеарности).AveBedrms = +0.339противAveRooms = -0.294— эти признаки имеют противоположные знаки, хотя большее количество комнат и спален в целом означает более крупный и дорогой дом. Это классический признак мультиколлинеарности:AveRoomsиAveBedrmsкоррелированы (r = 0.85), поэтому их коэффициенты компенсируют друг друга. Не интерпретируйте их по отдельности.Population = -0.002— очень близко к нулю после масштабирования. Население блока почти не влияет на предсказание при наличии остальных признаков.
Свободный член (2.07) — это предсказанное значение MedHouseVal, когда каждый масштабированный признак равен нулю, то есть когда все признаки находятся на уровне среднего по обучающей выборке. Он равен среднему целевых значений обучающей выборки и не имеет прямого содержательного смысла за рамками этого.
Шаг 6 — Предсказания для новых данных
# A new census block: high income, older house, San Francisco Bay Area
new_block = pd.DataFrame([[8.0, 41.0, 6.0, 1.0, 322, 2.5, 37.88, -122.23]],
columns=housing.feature_names)
new_block_scaled = scaler.transform(new_block)
prediction = model.predict(new_block_scaled)
print(f"Predicted median house value: ${prediction[0] * 100_000:,.0f}")Ожидаемый вывод:
Predicted median house value: $410,895Масштабировщик должен быть тем же самым, который был подогнан на обучающих данных. Никогда не переподгоняйте масштабировщик на новых данных — это сдвинет входные значения относительно тех, на которых обучалась модель.
Диагностика мультиколлинеарности
Мультиколлинеарность возникает, когда две или более независимых переменных сильно коррелированы между собой. Это не мешает модели делать точные предсказания, но делает индивидуальные коэффициенты ненадёжными и трудно интерпретируемыми. Коэффициенты могут стать большими, изменить знак или оказаться статистически незначимыми даже для действительно важных признаков.
Проверка с помощью матрицы корреляций
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
corr = df[housing.feature_names].corr()
print(corr.round(2))Ключевые пары для внимания:
| Пара признаков | Корреляция | Проблема |
|---|---|---|
AveRooms / AveBedrms | 0.85 | Высокая — коэффициенты компенсируют друг друга |
Latitude / Longitude | -0.93 | Очень высокая — географическое совместное движение |
MedInc / MedHouseVal (цель) | 0.69 | Хороший предиктор, не является проблемой коллинеарности |
Корреляция выше 0.80 между двумя признаками — предупреждающий сигнал. Когда вы её обнаруживаете, рассмотрите следующее:
- Удалите один из коррелированных признаков. Если в модели есть и
AveRooms, иAveBedrms, попробуйте убратьAveBedrmsи проверьте, сильно ли изменится предсказательная способность модели. - Объедините их. Создайте производный признак (например,
rooms_per_bedroom = AveRooms / AveBedrms), который отражает отношение без избыточности. - Используйте регуляризованную модель. Ridge-регрессия добавляет L2-штраф, который сжимает коррелированные коэффициенты друг к другу, стабилизируя их. Lasso (L1) может полностью обнулить избыточные признаки.
Анализ остатков
Остаток — это разность между фактическим значением и предсказанием модели: residual = y_actual - y_predicted. Построение графика остатков позволяет выяснить, выполняются ли допущения модели.
residuals = y_test - y_pred
print(f"Mean of residuals: {residuals.mean():.4f}") # should be close to 0
print(f"Std of residuals: {residuals.std():.4f}")Ожидаемый вывод:
Mean of residuals: 0.0035
Std of residuals: 0.7457Среднее близко к нулю — признак того, что модель в среднем несмещённая. Однако стандартное отклонение 0.75 (±$75 000) свидетельствует о значительном разбросе.
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Predicted vs Actual
axes[0].scatter(y_test, y_pred, alpha=0.2, s=8)
axes[0].plot([0, 5], [0, 5], 'r--')
axes[0].set_xlabel('Actual')
axes[0].set_ylabel('Predicted')
axes[0].set_title('Predicted vs Actual')
# Residuals vs Predicted
axes[1].scatter(y_pred, residuals, alpha=0.2, s=8)
axes[1].axhline(0, color='r', linestyle='--')
axes[1].set_xlabel('Predicted')
axes[1].set_ylabel('Residual')
axes[1].set_title('Residuals vs Predicted')
plt.tight_layout()
plt.savefig('residuals.png', dpi=120)
print("Saved residuals.png")На что обращать внимание:
- Predicted vs Actual (предсказанное vs фактическое) — в идеале точки лежат вдоль диагонали. Систематическое отклонение от диагонали (изгиб или уплощение при высоких значениях) означает, что линейное допущение нарушается в части диапазона.
- Residuals vs Predicted (остатки vs предсказанные) — в идеале остатки случайно рассеяны вокруг нуля при любых уровнях предсказания. Форма воронки (более широкий разброс при высоких предсказаниях) указывает на гетероскедастичность — ошибка модели непостоянна, что может делать интервальные оценки ненадёжными.
- Кластеры — отдельные группы на графике остатков могут указывать на то, что набор данных содержит подпопуляции (например, городские vs сельские районы), для которых требуются отдельные модели или дополнительные признаки.
Полный конвейер
Вот всё вышесказанное в виде единого запускаемого скрипта:
import numpy as np
import pandas as pd
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
# 1. Load data
housing = fetch_california_housing()
df = pd.DataFrame(housing.data, columns=housing.feature_names)
df['MedHouseVal'] = housing.target
# 2. Split — before any preprocessing
X = df[housing.feature_names]
y = df['MedHouseVal']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. Scale
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. Train
model = LinearRegression()
model.fit(X_train_scaled, y_train)
# 5. Evaluate
y_pred = model.predict(X_test_scaled)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")
print(f"R²: {r2:.4f}")
# 6. Coefficients (most important first)
coef_df = pd.DataFrame({
'Feature': housing.feature_names,
'Coefficient': model.coef_
}).sort_values('Coefficient', key=abs, ascending=False)
print(coef_df.to_string(index=False))Когда использовать множественную регрессию
Множественная регрессия — сильный первый выбор, когда:
- Связь между каждым признаком и целевой переменной приблизительно линейна
- Важна интерпретируемость — каждый коэффициент имеет понятный смысл
- Нужен быстрый базовый уровень перед попыткой сложных моделей
- У вас достаточно образцов относительно числа признаков (грубое правило: не менее 10–20 наблюдений на признак)
Рассмотрите альтернативы, когда:
| Ситуация | Альтернатива |
|---|---|
| Нелинейная зависимость признака и цели | Полиномиальная регрессия |
| Много признаков, риск переобучения | Ridge или Lasso (регуляризованные линейные модели) |
| Целевая переменная — категория, а не число | Логистическая регрессия |
| Сложные взаимодействия и нелинейность | Градиентный бустинг или случайные леса |
Распространённые ошибки
Подгонка масштабировщика на всех данных до разделения. Это приводит к утечке среднего и дисперсии тестовой выборки в обучение. Всегда сначала делите данные, затем подгоняйте масштабировщик только на обучающей части.
Добавление большего числа признаков всегда помогает. Добавление нерелевантных или избыточных признаков может снизить интерпретируемость, усилить мультиколлинеарность и ухудшить обобщаемость. Используйте доменные знания или метод отбора признаков для осознанного выбора.
Доверие коэффициентам при коррелированных признаках. Когда в модели есть и AveRooms, и AveBedrms, ни один из коэффициентов не отражает достоверно истинный эффект этой переменной. Проверяйте матрицу корреляций перед интерпретацией отдельных коэффициентов.
Игнорирование графиков остатков. R² = 0.58 выглядит приемлемым на бумаге, но паттерны остатков могут показать, что модель систематически ошибается для дорогостоящей недвижимости или определённых географических регионов.
Экстраполяция за пределы обучающего диапазона. Линейная модель, обученная на домах стоимостью от $50 000 до $500 000, не должна применяться для предсказания объектов за $5 000 000. Убедитесь, что новые входные данные находятся в диапазоне, наблюдавшемся при обучении.
Следующие шаги
- Линейная регрессия — основа: простая (однопризнаковая) регрессия и метод МНК, объяснённые в деталях
- Полиномиальная регрессия — расширение линейной модели для кривых путём добавления полиномиальных признаков
- Масштабирование признаков — глубокое погружение в StandardScaler, MinMaxScaler и RobustScaler
- Разделение на обучающую и тестовую выборки — почему правильное разделение критически важно и как его выполнять без утечки данных
- Перекрёстная проверка — более надёжная альтернатива однократному разделению на обучающую и тестовую выборки