Урок курса
Коэффициенты линейной регрессии
Машинное обучение с нуля на 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. Проверка вызывает функцию также с другими числовыми строками и коэффициентами.
Продолжить с проверкой и прогрессом
Откройте интерактивный раннер с заданиями урока.
