Урок курса

Коэффициенты линейной регрессии

Машинное обучение с нуля на Python: первая модель

На предыдущих уроках мы вызывали model.fit и model.predict как инструмент, не заглядывая внутрь результата. Сейчас посмотрим, что именно модель сохраняет после обучения — и что эти числа означают.

Атрибуты coef_ и intercept_: что хранит модель после fit и как их прочитать

После вызова model.fit(X_train, y_train) объект LinearRegression хранит два атрибута. model.intercept_ — одно число, свободный член. model.coef_ — NumPy-массив, где i-й элемент соответствует i-му столбцу того DataFrame, который был передан в fit. Длина массива равна числу признаков: если их 10, в coef_ ровно 10 чисел.

Важный момент: оба атрибута появляются только после fit. До обучения атрибут coef_ у объекта просто отсутствует, и обращение к нему вызывает AttributeError. Это стандартное поведение всех оценщиков sklearn: атрибуты с подчёркиванием на конце всегда означают «посчитано в результате fit». Схожая ошибка — NotFittedError — возникает, если вызвать model.predict до fit.

Чтобы увидеть коэффициенты осмысленно, нужно сопоставить их с именами признаков. Порядок элементов в model.coef_ совпадает с порядком столбцов в том X_train, который был передан в fit. Поэтому безопаснее зафиксировать список имён сразу после разделения данных и использовать именно его при выводе:

from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression

bunch = load_diabetes(as_frame=True)
X = bunch.data
y = bunch.target

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

# Сохраняем порядок столбцов до fit
feature_names = list(X_train.columns)

model = LinearRegression()
model.fit(X_train, y_train)

for name, coef in zip(feature_names, model.coef_):
    print(f"{name}: {coef:.4f}")

print("intercept:", model.intercept_)

Датасет diabetes содержит 10 числовых признаков (возраст, индекс массы тела, давление и т.д.), все уже стандартизованы авторами датасета. После fit каждая строка вывода покажет пару «имя признака — его коэффициент». intercept_ — отдельное число, не входящее в coef_.

Ключевое правило: model.coef_ фиксирует порядок столбцов на момент вызова fit. Если после обучения переупорядочить DataFrame (например, переставить столбцы через iloc или переиндексировать), сам coef_ не изменится — он по-прежнему соответствует тому порядку, который был при обучении. Поэтому сопоставлять коэффициенты с именами нужно именно через список, сохранённый до fit, а не через текущее состояние DataFrame.

Единицы коэффициентов и граница их интерпретации без причинности

Каждый коэффициент из model.coef_ отвечает на один конкретный вопрос: на сколько единиц изменится предсказание модели, если этот признак вырастет на одну его единицу, а все остальные признаки останутся на прежних значениях. Единица коэффициента — [единица цели] / [единица признака].

В датасете diabetes признаки масштабированы нестандартным способом: каждый столбец центрируется, а затем делится на стандартное отклонение, умноженное на sqrt(n_samples) — то есть на sqrt(442). Поэтому одна единица масштабированного столбца — это не одно исходное стандартное отклонение, а примерно sqrt(442) ≈ 21 исходных стандартных отклонений. Коэффициент при bmi, например, показывает, на сколько пунктов целевой переменной меняется предсказание при росте уже масштабированного bmi на 1. Для целей этого урока достаточно помнить: коэффициент всегда привязан к единицам того столбца, который фактически подаётся в модель.

Граница интерпретации проходит вот здесь: коэффициент описывает поведение модели, а не реального мира. Правильная формулировка — «при росте признака X на 1 единицу предсказание модели меняется на N единиц». Неправильная — «X вызывает изменение цели на N единиц».

Почему это важно? Коэффициент зависит от того, какие другие признаки включены в модель, как они коррелируют между собой и какие данные попали в обучающую выборку. Добавление или удаление одного признака может изменить коэффициенты других признаков — особенно если те коррелируют с добавленным или удалённым. Это значит, что одно и то же число может принять другое значение в другой модели на тех же данных, а причинный эффект от этого не меняется.

Корреляция между признаками особенно заметна в медицинских данных: bmi и несколько других метаболических показателей связаны между собой. Когда признаки коррелируют, модель распределяет «объяснительную нагрузку» между ними произвольным образом, и коэффициент при одном из них несёт лишь часть общей картины. Говорить о причинности в таком случае — значит приписывать число, зависящее от состава признаков, реальному механизму.

Предсказание для одной строки и ручная проверка формулы

Чтобы получить предсказание для одного конкретного объекта, нужно передать в model.predict не словарь и не Series, а DataFrame с одной строкой и теми же столбцами, что у X_train. В актуальных версиях scikit-learn передача голого словаря или Series вызывает предупреждение или ошибку.

import pandas as pd

row = pd.DataFrame([{
    'age': X_test.iloc[0]['age'],
    'sex': X_test.iloc[0]['sex'],
    'bmi': X_test.iloc[0]['bmi'],
    'bp':  X_test.iloc[0]['bp'],
    's1': X_test.iloc[0]['s1'],
    's2': X_test.iloc[0]['s2'],
    's3': X_test.iloc[0]['s3'],
    's4': X_test.iloc[0]['s4'],
    's5': X_test.iloc[0]['s5'],
    's6': X_test.iloc[0]['s6'],
}])

pred = model.predict(row)
print("predict:", pred[0])

Результат model.predict — NumPy-массив из одного элемента; нужное число берётся через [0].

Чтобы убедиться, что коэффициенты прочитаны правильно, можно воспроизвести то же значение вручную по формуле линейной регрессии:

import numpy as np

manual = model.intercept_ + np.sum(model.coef_ * row.values[0])
print("manual:", manual)

row.values[0] — NumPy-массив значений признаков в том же порядке, что model.coef_. Поэлементное произведение и сумма дают intercept_ + Σ(coef_[i] * x[i]) — именно ту формулу, которую решает линейная регрессия.

Если pred[0] и manual совпадают (с точностью до floating-point округления), это подтверждает: коэффициенты сопоставлены с признаками верно, порядок столбцов не нарушен. Расхождение в нескольких знаках после запятой — нормальная арифметика с плавающей точкой, не ошибка. Если числа расходятся значительно, стоит проверить порядок столбцов в row относительно X_train.

Попробуйте решить

Два способа получить один прогноз

Линейная регрессия привлекает аналитиков своей прозрачностью: каждое предсказание можно разложить на слагаемые, где каждому признаку соответствует свой коэффициент.

Что уже дано

В редакторе есть импорты load_diabetes, train_test_split, LinearRegression. Данные и модель предыдущего урока не сохраняются. Видимый шаблон содержит заготовку manual_predict(row, coefficients, intercept); аргументы — последовательности чисел для одной строки и коэффициентов и числовой свободный член. Скрытая подготовка не создаёт данные.

Что нужно сделать

Реализуйте manual_predict так, чтобы она вычисляла число по переданным коэффициентам и значениям строки, не вызывая model.predict. Затем загрузите diabetes, совместно разделите данные (test_size=0.2, random_state=0), обучите model только на train и возьмите первую строку X_test как X_new с исходным порядком признаков. Получите prediction через модель и manual_prediction через свою функцию. В matches сохраните результат сравнения с допуском 1e-8. Фактический ответ этой строки не используйте.

Ввод и вывод

stdin не используется. Печатать через print в stdout ничего не нужно. Проверяются функция manual_predict, верхнеуровневые X_new, model, prediction, manual_prediction, matches. Проверка вызывает функцию также с другими числовыми строками и коэффициентами.

РешениеPython
Без регистрации · результат не сохраняется

Продолжить с проверкой и прогрессом

Откройте интерактивный раннер с заданиями урока.

Перейти к интерактивному уроку