Spec-Zone.ru › scikit-learn

Примечание

Перейти к концу для скачивания полного примера кода. Или запустить этот пример в браузере с помощью JupyterLite или Binder

Общие ошибки при интерпретации коэффициентов линейных моделей

В линейных моделях целевое значение моделируется как линейная комбинация признаков (см. раздел «Линейные модели» в руководстве пользователя, для описания набора линейных моделей, доступных в scikit-learn). Коэффициенты в многомерных линейных моделях представляют собой взаимосвязь между заданным признаком \(X_i\) и целевым значением \(y\), при условии, что все остальные признаки остаются постоянными (условная зависимость). Это отличается от построения графика \(X_i\) против \(y\) и подгонки линейной зависимости: в этом случае при оценке учитываются все возможные значения других признаков (маргинальная зависимость).

Этот пример предоставит некоторые подсказки по интерпретации коэффициентов в линейных моделях, указывая на проблемы, возникающие, когда либо линейная модель не подходит для описания набора данных, либо когда признаки коррелированы.

Примечание

Обратите внимание, что признаки \(X\) и результат \(y\) обычно являются результатом неизвестного для нас процесса генерации данных. Модели машинного обучения обучаются на примере, чтобы приблизить неочевидную математическую функцию, связывающую \(X\) с \(y\). В результате любая интерпретация, сделанная относительно модели, может не обобщаться на реальный процесс генерации данных. Это особенно верно, когда модель имеет низкое качество или когда выборка данных не является репрезентативной для генеральной совокупности.

Мы будем использовать данные из “Общей переписи населения” 1985 года для прогнозирования заработной платы в зависимости от различных признаков, таких как опыт работы, возраст или образование.

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy as sp
import seaborn as sns

Набор данных: заработная плата

Мы получаем данные из OpenML. Обратите внимание, что установка параметра as_frame в значение True позволит получить данные в виде pandas DataFrame.

from sklearn.datasets import fetch_openml

survey = fetch_openml(data_id=534, as_frame=True)

Затем мы определяем признаки X и целевые значения y: столбец WAGE является нашей целевой переменной (т.е. переменной, которую мы хотим предсказать).

X = survey.data[survey.feature_names]
X.describe(include="all")
EDUCATION SOUTH SEX EXPERIENCE UNION AGE RACE OCCUPATION SECTOR MARR
count 534.000000 534 534 534.000000 534 534.000000 534 534 534 534
unique NaN 2 2 NaN 2 NaN 3 6 3 2
top NaN no male NaN not_member NaN White Other Other Married
freq NaN 378 289 NaN 438 NaN 440 156 411 350
mean 13.018727 NaN NaN 17.822097 NaN 36.833333 NaN NaN NaN NaN
std 2.615373 NaN NaN 12.379710 NaN 11.726573 NaN NaN NaN NaN
min 2.000000 NaN NaN 0.000000 NaN 18.000000 NaN NaN NaN NaN
25% 12.000000 NaN NaN 8.000000 NaN 28.000000 NaN NaN NaN NaN
50% 12.000000 NaN NaN 15.000000 NaN 35.000000 NaN NaN NaN NaN
75% 15.000000 NaN NaN 26.000000 NaN 44.000000 NaN NaN NaN NaN
max 18.000000 NaN NaN 55.000000 NaN 64.000000 NaN NaN NaN NaN


Обратите внимание, что набор данных содержит категориальные и числовые переменные. Это следует учитывать при последующей предобработке набора данных.

X.head()
EDUCATION SOUTH SEX EXPERIENCE UNION AGE RACE OCCUPATION SECTOR MARR
0 8 no female 21 not_member 35 Hispanic Other Manufacturing Married
1 9 no female 42 not_member 57 White Other Manufacturing Married
2 12 no male 1 not_member 19 White Other Manufacturing Unmarried
3 12 no male 4 not_member 22 White Other Other Unmarried
4 12 no male 17 not_member 35 White Other Other Married


Наша целевая переменная для предсказания: заработная плата. Заработная плата описывается как число с плавающей точкой в долларах в час.

y = survey.target.values.ravel()
survey.target.head()
0    5.10
1    4.95
2    6.67
3    4.00
4    7.50
Name: WAGE, dtype: float64

Мы разделим выборку на обучающую и тестовую выборки. Только обучающая выборка будет использоваться в последующем исследовательском анализе. Это способ смоделировать реальную ситуацию, когда прогнозы выполняются на неизвестной цели, и мы не хотим, чтобы наш анализ и решения были предвзяты знанием тестовых данных.

from sklearn.model_selection import train_test_split

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

Сначала давайте получим некоторые сведения, изучив распределения переменных и парные взаимосвязи между ними. Будут использованы только числовые переменные. На следующем графике каждая точка представляет собой образец.

train_dataset = X_train.copy()
train_dataset.insert(0, "WAGE", y_train)
_ = sns.pairplot(train_dataset, kind="reg", diag_kind="kde")
plot linear model coefficient interpretation

Внимательный взгляд на распределение WAGE показывает, что у него длинный хвост. По этой причине мы должны взять его логарифм, чтобы приблизительно преобразовать его в нормальное распределение (такие линейные модели, как ridge или lasso, лучше всего работают при нормальном распределении ошибки).

WAGE увеличивается при увеличении EDUCATION. Обратите внимание, что зависимость между WAGE и EDUCATION, представленная здесь, является маргинальной зависимостью, т.е. она описывает поведение конкретной переменной без сохранения других фиксированными.

Также EXPERIENCE и AGE сильно линейно коррелированы.

Этапы машинного обучения

Для разработки нашей системы машинного обучения мы сначала вручную проверяем тип данных, с которыми имеем дело:

survey.data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 534 entries, 0 to 533
Data columns (total 10 columns):
 #   Column      Non-Null Count  Dtype
---  ------      --------------  -----
 0   EDUCATION   534 non-null    int64
 1   SOUTH       534 non-null    category
 2   SEX         534 non-null    category
 3   EXPERIENCE  534 non-null    int64
 4   UNION       534 non-null    category
 5   AGE         534 non-null    int64
 6   RACE        534 non-null    category
 7   OCCUPATION  534 non-null    category
 8   SECTOR      534 non-null    category
 9   MARR        534 non-null    category
dtypes: category(7), int64(3)
memory usage: 17.2 KB

Как видно ранее, набор данных содержит столбцы с разными типами данных, и нам необходимо применять специфическую предобработку для каждого типа данных. В частности, категориальные переменные не могут быть включены в линейную модель, если сначала не закодированы как целые числа. Кроме того, чтобы избежать того, что категориальные признаки обрабатывались как упорядоченные значения, нам необходимо выполнить их кодирование с помощью one-hot кодирования. Наш препроцессор будет:

  • one-hot кодировать (т.е., генерировать столбец по категориям) категориальные столбцы, только для категориальных переменных, не являющихся бинарными;
  • в качестве первого подхода (мы увидим позже, как нормализация числовых значений повлияет на наш обсуждение), сохранить числовые значения такими, как они есть.
from sklearn.compose import make_column_transformer
from sklearn.preprocessing import OneHotEncoder

categorical_columns = ["RACE", "OCCUPATION", "SECTOR", "MARR", "UNION", "SEX", "SOUTH"]
numerical_columns = ["EDUCATION", "EXPERIENCE", "AGE"]

preprocessor = make_column_transformer(
    (OneHotEncoder(drop="if_binary"), categorical_columns),
    remainder="passthrough",
    verbose_feature_names_out=False,  # avoid to prepend the preprocessor names
)

Для описания набора данных как линейной модели мы используем регрессор Ridge с очень маленькой регуляризацией и для моделирования логарифма WAGE.

from sklearn.compose import TransformedTargetRegressor
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline

model = make_pipeline(
    preprocessor,
    TransformedTargetRegressor(
        regressor=Ridge(alpha=1e-10), func=np.log10, inverse_func=sp.special.exp10
    ),
)

Обработка набора данных

Сначала мы подгоняем модель.

model.fit(X_train, y_train)
Pipeline(steps=[('columntransformer',
                 ColumnTransformer(remainder='passthrough',
                                   transformers=[('onehotencoder',
                                                  OneHotEncoder(drop='if_binary'),
                                                  ['RACE', 'OCCUPATION',
                                                   'SECTOR', 'MARR', 'UNION',
                                                   'SEX', 'SOUTH'])],
                                   verbose_feature_names_out=False)),
                ('transformedtargetregressor',
                 TransformedTargetRegressor(func=<ufunc 'log10'>,
                                            inverse_func=<ufunc 'exp10'>,
                                            regressor=Ridge(alpha=1e-10)))])
В среде Jupyter, пожалуйста, перезапустите этот ячейку, чтобы показать представление HTML, или доверьтесь блокноту.
На GitHub представление HTML не может быть отображено, попробуйте загрузить эту страницу с nbviewer.org.
Pipeline(steps=[('columntransformer',
                 ColumnTransformer(remainder='passthrough',
                                   transformers=[('onehotencoder',
                                                  OneHotEncoder(drop='if_binary'),
                                                  ['RACE', 'OCCUPATION',
                                                   'SECTOR', 'MARR', 'UNION',
                                                   'SEX', 'SOUTH'])],
                                   verbose_feature_names_out=False)),
                ('transformedtargetregressor',
                 TransformedTargetRegressor(func=<ufunc 'log10'>,
                                            inverse_func=<ufunc 'exp10'>,
                                            regressor=Ridge(alpha=1e-10)))])
ColumnTransformer(remainder='passthrough',
                  transformers=[('onehotencoder',
                                 OneHotEncoder(drop='if_binary'),
                                 ['RACE', 'OCCUPATION', 'SECTOR', 'MARR',
                                  'UNION', 'SEX', 'SOUTH'])],
                  verbose_feature_names_out=False)
TransformedTargetRegressor(func=<ufunc 'log10'>, inverse_func=<ufunc 'exp10'>,
                           regressor=Ridge(alpha=1e-10))
Ridge(alpha=1e-10)
Ridge(alpha=1e-10)


Затем мы проверяем производительность вычисленной модели, строя её предсказания на тестовом наборе и вычисляя, например, медианную абсолютную ошибку модели.

from sklearn.metrics import PredictionErrorDisplay, median_absolute_error

mae_train = median_absolute_error(y_train, model.predict(X_train))
y_pred = model.predict(X_test)
mae_test = median_absolute_error(y_test, y_pred)
scores = {
    "MedAE on training set": f"{mae_train:.2f} $/hour",
    "MedAE on testing set": f"{mae_test:.2f} $/hour",
}
_, ax = plt.subplots(figsize=(5, 5))
display = PredictionErrorDisplay.from_predictions(
    y_test, y_pred, kind="actual_vs_predicted", ax=ax, scatter_kwargs={"alpha": 0.5}
)
ax.set_title("Ridge model, small regularization")
for name, score in scores.items():
    ax.plot([], [], " ", label=f"{name}: {score}")
ax.legend(loc="upper left")
plt.tight_layout()
Ridge model, small regularization

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

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

Интерпретация коэффициентов: масштаб имеет значение

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

feature_names = model[:-1].get_feature_names_out()

coefs = pd.DataFrame(
    model[-1].regressor_.coef_,
    columns=["Coefficients"],
    index=feature_names,
)

coefs
Coefficients
RACE_Hispanic -0.013520
RACE_Other -0.009077
RACE_White 0.022593
OCCUPATION_Clerical 0.000045
OCCUPATION_Management 0.090528
OCCUPATION_Other -0.025102
OCCUPATION_Professional 0.071964
OCCUPATION_Sales -0.046636
OCCUPATION_Service -0.091053
SECTOR_Construction -0.000198
SECTOR_Manufacturing 0.031255
SECTOR_Other -0.031026
MARR_Unmarried -0.032405
UNION_not_member -0.117154
SEX_male 0.090808
SOUTH_yes -0.033823
EDUCATION 0.054699
EXPERIENCE 0.035005
AGE -0.030867


Коэффициент AGE выражен в «долларах/час на год жизни», а коэффициент EDUCATION — в «долларах/час на год образования». Это представление коэффициентов имеет преимущество в том, что оно чётко показывает практические предсказания модели: увеличение на 1 год возраста означает снижение на 0,030867 доллара/час, а увеличение на 1 год образования означает повышение на 0,054699 доллара/час. С другой стороны, категориальные переменные (например, UNION или SEX) — это безразмерные числа, принимающие значения 0 или 1. Их коэффициенты выражены в долларах/час. Тогда мы не можем сравнивать величину разных коэффициентов, так как у признаков разные естественные масштабы, а следовательно, и диапазоны значений, из-за разных единиц измерения. Это более заметно, если мы отобразим коэффициенты.

coefs.plot.barh(figsize=(9, 7))
plt.title("Ridge model, small regularization")
plt.axvline(x=0, color=".5")
plt.xlabel("Raw coefficient values")
plt.subplots_adjust(left=0.3)
Ridge model, small regularization

Действительно, из графика выше наиболее важным фактором, определяющим WAGE, кажется переменная UNION, даже если наша интуиция подсказывает, что такие переменные, как EXPERIENCE, должны иметь большее влияние.

Взгляд на график коэффициентов для оценки важности признаков может быть вводящим в заблуждение, так как некоторые из них изменяются в небольшом масштабе, в то время как другие, например AGE, изменяются гораздо больше, на несколько десятилетий.

Это видно, если сравнить стандартные отклонения различных признаков.

X_train_preprocessed = pd.DataFrame(
    model[:-1].transform(X_train), columns=feature_names
)

X_train_preprocessed.std(axis=0).plot.barh(figsize=(9, 7))
plt.title("Feature ranges")
plt.xlabel("Std. dev. of feature values")
plt.subplots_adjust(left=0.3)
Feature ranges

Умножение коэффициентов на стандартное отклонение соответствующего признака привело бы к приведению всех коэффициентов к одинаковой единице измерения. Как мы увидим дальше, это эквивалентно нормализации числовых переменных к их стандартному отклонению, так как \(y = \sum{coef_i \times X_i} = \sum{(coef_i \times std_i) \times (X_i / std_i)}\).

Таким образом, мы подчеркиваем, что чем больше дисперсия признака, тем больше вес соответствующего коэффициента на выходе при прочих равных условиях.

coefs = pd.DataFrame(
    model[-1].regressor_.coef_ * X_train_preprocessed.std(axis=0),
    columns=["Coefficient importance"],
    index=feature_names,
)
coefs.plot(kind="barh", figsize=(9, 7))
plt.xlabel("Coefficient values corrected by the feature's std. dev.")
plt.title("Ridge model, small regularization")
plt.axvline(x=0, color=".5")
plt.subplots_adjust(left=0.3)
Ridge model, small regularization

Теперь, когда коэффициенты были масштабированы, мы можем их безопасно сравнивать.

Предупреждение

Почему график выше показывает, что увеличение возраста приводит к снижению заработной платы? Почему первоначальная диаграмма рассеяния пар говорит об обратном?

График выше показывает нам зависимости между конкретным признаком и целевым значением, когда все остальные признаки остаются неизменными, то есть условные зависимости. Увеличение возраста приведёт к снижению заработной платы, когда все остальные признаки останутся неизменными. Напротив, увеличение опыта приведёт к увеличению заработной платы, когда все остальные признаки останутся неизменными. Также AGE, EXPERIENCE и EDUCATION — это три переменные, которые в наибольшей степени влияют на модель.

Интерпретация коэффициентов: осторожность в отношении причинно-следственных связей

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

В нашем конкретном случае мы можем рассматривать образование человека как заместитель его профессиональной пригодности, реальной переменной, которая нас интересует, но которую мы не можем наблюдать. Мы, конечно, хотели бы думать, что более длительное обучение в школе повысит техническую компетентность, но также вполне возможно, что причинно-следственная связь действует и в обратную сторону. То есть, те, кто технически компетентен, склонны учиться дольше.

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

Это смешение эффектов становится проблематичным при рассмотрении каких-либо вмешательств, например, государственных субсидий на высшее образование или рекламных материалов, побуждающих людей получить высшее образование. Полезность таких мер может оказаться завышенной, особенно если степень смешения значительная. Наша модель предсказывает увеличение почасовой заработной платы на 0,054699 за каждый год образования. Фактическое причинное воздействие может быть ниже из-за этого смешения.

Проверка изменчивости коэффициентов

Мы можем проверить изменчивость коэффициентов с помощью перекрёстной проверки: это форма возмущения данных (связанная с перевыборкой).

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

from sklearn.model_selection import RepeatedKFold, cross_validate

cv = RepeatedKFold(n_splits=5, n_repeats=5, random_state=0)
cv_model = cross_validate(
    model,
    X,
    y,
    cv=cv,
    return_estimator=True,
    n_jobs=2,
)

coefs = pd.DataFrame(
    [
        est[-1].regressor_.coef_ * est[:-1].transform(X.iloc[train_idx]).std(axis=0)
        for est, (train_idx, _) in zip(cv_model["estimator"], cv.split(X, y))
    ],
    columns=feature_names,
)
plt.figure(figsize=(9, 7))
sns.stripplot(data=coefs, orient="h", palette="dark:k", alpha=0.5)
sns.boxplot(data=coefs, orient="h", color="cyan", saturation=0.5, whis=10)
plt.axvline(x=0, color=".5")
plt.xlabel("Coefficient importance")
plt.title("Coefficient importance and its variability")
plt.suptitle("Ridge model, small regularization")
plt.subplots_adjust(left=0.3)
Ridge model, small regularization, Coefficient importance and its variability

Проблема коррелированных переменных

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

Чтобы проверить эту интерпретацию, мы строим график изменчивости коэффициентов AGE и EXPERIENCE.

plt.ylabel("Age coefficient")
plt.xlabel("Experience coefficient")
plt.grid(True)
plt.xlim(-0.4, 0.5)
plt.ylim(-0.4, 0.5)
plt.scatter(coefs["AGE"], coefs["EXPERIENCE"])
_ = plt.title("Co-variations of coefficients for AGE and EXPERIENCE across folds")
Co-variations of coefficients for AGE and EXPERIENCE across folds

Заполняются две области: когда коэффициент EXPERIENCE положителен, коэффициент AGE отрицателен, и наоборот.

Чтобы продолжить, мы удаляем один из двух признаков и проверяем, как это повлияет на стабильность модели.

column_to_drop = ["AGE"]

cv_model = cross_validate(
    model,
    X.drop(columns=column_to_drop),
    y,
    cv=cv,
    return_estimator=True,
    n_jobs=2,
)

coefs = pd.DataFrame(
    [
        est[-1].regressor_.coef_
        * est[:-1].transform(X.drop(columns=column_to_drop).iloc[train_idx]).std(axis=0)
        for est, (train_idx, _) in zip(cv_model["estimator"], cv.split(X, y))
    ],
    columns=feature_names[:-1],
)
plt.figure(figsize=(9, 7))
sns.stripplot(data=coefs, orient="h", palette="dark:k", alpha=0.5)
sns.boxplot(data=coefs, orient="h", color="cyan", saturation=0.5)
plt.axvline(x=0, color=".5")
plt.title("Coefficient importance and its variability")
plt.xlabel("Coefficient importance")
plt.suptitle("Ridge model, small regularization, AGE dropped")
plt.subplots_adjust(left=0.3)
Ridge model, small regularization, AGE dropped, Coefficient importance and its variability

Оценка коэффициента EXPERIENCE теперь показывает значительно меньшую изменчивость. EXPERIENCE остается важным для всех моделей, обученных во время кросс-валидации.

Предобработка числовых переменных

Как было сказано выше (см. «Машинный цикл»), мы также можем выбрать масштабирование числовых значений перед обучением модели. Это может быть полезно, когда мы применяем аналогичное количество регуляризации ко всем из них в методе Ridge. Препроцессор переопределяется для вычитания среднего значения и масштабирования переменных до единичной дисперсии.

from sklearn.preprocessing import StandardScaler

preprocessor = make_column_transformer(
    (OneHotEncoder(drop="if_binary"), categorical_columns),
    (StandardScaler(), numerical_columns),
)

Модель останется неизменной.

model = make_pipeline(
    preprocessor,
    TransformedTargetRegressor(
        regressor=Ridge(alpha=1e-10), func=np.log10, inverse_func=sp.special.exp10
    ),
)
model.fit(X_train, y_train)
Pipeline(steps=[('columntransformer',
                 ColumnTransformer(transformers=[('onehotencoder',
                                                  OneHotEncoder(drop='if_binary'),
                                                  ['RACE', 'OCCUPATION',
                                                   'SECTOR', 'MARR', 'UNION',
                                                   'SEX', 'SOUTH']),
                                                 ('standardscaler',
                                                  StandardScaler(),
                                                  ['EDUCATION', 'EXPERIENCE',
                                                   'AGE'])])),
                ('transformedtargetregressor',
                 TransformedTargetRegressor(func=<ufunc 'log10'>,
                                            inverse_func=<ufunc 'exp10'>,
                                            regressor=Ridge(alpha=1e-10)))])
В среде Jupyter, пожалуйста, перезапустите ячейку, чтобы отобразить HTML-представление, или доверьтесь блокноту.
На GitHub HTML-представление не может отобразиться, пожалуйста, попробуйте загрузить эту страницу с nbviewer.org.
Pipeline(steps=[('columntransformer',
                 ColumnTransformer(transformers=[('onehotencoder',
                                                  OneHotEncoder(drop='if_binary'),
                                                  ['RACE', 'OCCUPATION',
                                                   'SECTOR', 'MARR', 'UNION',
                                                   'SEX', 'SOUTH']),
                                                 ('standardscaler',
                                                  StandardScaler(),
                                                  ['EDUCATION', 'EXPERIENCE',
                                                   'AGE'])])),
                ('transformedtargetregressor',
                 TransformedTargetRegressor(func=<ufunc 'log10'>,
                                            inverse_func=<ufunc 'exp10'>,
                                            regressor=Ridge(alpha=1e-10)))])
ColumnTransformer(transformers=[('onehotencoder',
                                 OneHotEncoder(drop='if_binary'),
                                 ['RACE', 'OCCUPATION', 'SECTOR', 'MARR',
                                  'UNION', 'SEX', 'SOUTH']),
                                ('standardscaler', StandardScaler(),
                                 ['EDUCATION', 'EXPERIENCE', 'AGE'])])
['RACE', 'OCCUPATION', 'SECTOR', 'MARR', 'UNION', 'SEX', 'SOUTH']
OneHotEncoder(drop='if_binary')
['EDUCATION', 'EXPERIENCE', 'AGE']
StandardScaler()
TransformedTargetRegressor(func=<ufunc 'log10'>, inverse_func=<ufunc 'exp10'>,
                           regressor=Ridge(alpha=1e-10))
Ridge(alpha=1e-10)
Ridge(alpha=1e-10)


Опять же, мы проверяем производительность вычисленной модели, например, с помощью медианной абсолютной ошибки модели и коэффициента R-квадрат.

mae_train = median_absolute_error(y_train, model.predict(X_train))
y_pred = model.predict(X_test)
mae_test = median_absolute_error(y_test, y_pred)
scores = {
    "MedAE on training set": f"{mae_train:.2f} $/hour",
    "MedAE on testing set": f"{mae_test:.2f} $/hour",
}

_, ax = plt.subplots(figsize=(5, 5))
display = PredictionErrorDisplay.from_predictions(
    y_test, y_pred, kind="actual_vs_predicted", ax=ax, scatter_kwargs={"alpha": 0.5}
)
ax.set_title("Ridge model, small regularization")
for name, score in scores.items():
    ax.plot([], [], " ", label=f"{name}: {score}")
ax.legend(loc="upper left")
plt.tight_layout()
Ridge model, small regularization

Для анализа коэффициентов масштабирование на этот раз не требуется, так как оно было выполнено на этапе предобработки.

coefs = pd.DataFrame(
    model[-1].regressor_.coef_,
    columns=["Coefficients importance"],
    index=feature_names,
)
coefs.plot.barh(figsize=(9, 7))
plt.title("Ridge model, small regularization, normalized variables")
plt.xlabel("Raw coefficient values")
plt.axvline(x=0, color=".5")
plt.subplots_adjust(left=0.3)
Ridge model, small regularization, normalized variables

Теперь мы проверяем коэффициенты по нескольким складкам кросс-валидации. Как и в предыдущем примере, нам не нужно масштабировать коэффициенты по стандартному отклонению значений признаков, так как это масштабирование уже было выполнено на этапе предобработки в цикле.

cv_model = cross_validate(
    model,
    X,
    y,
    cv=cv,
    return_estimator=True,
    n_jobs=2,
)
coefs = pd.DataFrame(
    [est[-1].regressor_.coef_ for est in cv_model["estimator"]], columns=feature_names
)
plt.figure(figsize=(9, 7))
sns.stripplot(data=coefs, orient="h", palette="dark:k", alpha=0.5)
sns.boxplot(data=coefs, orient="h", color="cyan", saturation=0.5, whis=10)
plt.axvline(x=0, color=".5")
plt.title("Coefficient variability")
plt.subplots_adjust(left=0.3)
Coefficient variability

Результат довольно похож на случай без нормализации.

Линейные модели с регуляризацией

На практике машинного обучения регрессия с гребнем чаще используется с нетривиальной регуляризацией.

Выше мы ограничили эту регуляризацию очень малым значением. Регуляризация улучшает обусловленность задачи и уменьшает дисперсию оценок. RidgeCV применяет перекрестную проверку, чтобы определить, какое значение параметра регуляризации (alpha) лучше всего подходит для прогнозирования.

from sklearn.linear_model import RidgeCV

alphas = np.logspace(-10, 10, 21)  # alpha values to be chosen from by cross-validation
model = make_pipeline(
    preprocessor,
    TransformedTargetRegressor(
        regressor=RidgeCV(alphas=alphas),
        func=np.log10,
        inverse_func=sp.special.exp10,
    ),
)
model.fit(X_train, y_train)
Pipeline(steps=[('columntransformer',
                 ColumnTransformer(transformers=[('onehotencoder',
                                                  OneHotEncoder(drop='if_binary'),
                                                  ['RACE', 'OCCUPATION',
                                                   'SECTOR', 'MARR', 'UNION',
                                                   'SEX', 'SOUTH']),
                                                 ('standardscaler',
                                                  StandardScaler(),
                                                  ['EDUCATION', 'EXPERIENCE',
                                                   'AGE'])])),
                ('transformedtargetregressor',
                 TransformedTargetRegressor(func=<ufunc 'log10'>,
                                            inverse_func=<ufunc 'exp10'>,
                                            regressor=RidgeCV(alphas=array([1.e-10, 1.e-09, 1.e-08, 1.e-07, 1.e-06, 1.e-05, 1.e-04, 1.e-03,
       1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05,
       1.e+06, 1.e+07, 1.e+08, 1.e+09, 1.e+10]))))])
В среде Jupyter, пожалуйста, перезапустите эту ячейку, чтобы показать HTML-представление, или доверьтесь блокноту.
На GitHub HTML-представление невозможно отобразить, пожалуйста, попробуйте загрузить эту страницу с nbviewer.org.
Pipeline(steps=[('columntransformer',
                 ColumnTransformer(transformers=[('onehotencoder',
                                                  OneHotEncoder(drop='if_binary'),
                                                  ['RACE', 'OCCUPATION',
                                                   'SECTOR', 'MARR', 'UNION',
                                                   'SEX', 'SOUTH']),
                                                 ('standardscaler',
                                                  StandardScaler(),
                                                  ['EDUCATION', 'EXPERIENCE',
                                                   'AGE'])])),
                ('transformedtargetregressor',
                 TransformedTargetRegressor(func=<ufunc 'log10'>,
                                            inverse_func=<ufunc 'exp10'>,
                                            regressor=RidgeCV(alphas=array([1.e-10, 1.e-09, 1.e-08, 1.e-07, 1.e-06, 1.e-05, 1.e-04, 1.e-03,
       1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05,
       1.e+06, 1.e+07, 1.e+08, 1.e+09, 1.e+10]))))])
ColumnTransformer(transformers=[('onehotencoder',
                                 OneHotEncoder(drop='if_binary'),
                                 ['RACE', 'OCCUPATION', 'SECTOR', 'MARR',
                                  'UNION', 'SEX', 'SOUTH']),
                                ('standardscaler', StandardScaler(),
                                 ['EDUCATION', 'EXPERIENCE', 'AGE'])])
['RACE', 'OCCUPATION', 'SECTOR', 'MARR', 'UNION', 'SEX', 'SOUTH']
OneHotEncoder(drop='if_binary')
['EDUCATION', 'EXPERIENCE', 'AGE']
StandardScaler()
TransformedTargetRegressor(func=<ufunc 'log10'>, inverse_func=<ufunc 'exp10'>,
                           regressor=RidgeCV(alphas=array([1.e-10, 1.e-09, 1.e-08, 1.e-07, 1.e-06, 1.e-05, 1.e-04, 1.e-03,
       1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05,
       1.e+06, 1.e+07, 1.e+08, 1.e+09, 1.e+10])))
RidgeCV(alphas=array([1.e-10, 1.e-09, 1.e-08, 1.e-07, 1.e-06, 1.e-05, 1.e-04, 1.e-03,
       1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05,
       1.e+06, 1.e+07, 1.e+08, 1.e+09, 1.e+10]))
RidgeCV(alphas=array([1.e-10, 1.e-09, 1.e-08, 1.e-07, 1.e-06, 1.e-05, 1.e-04, 1.e-03,
       1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05,
       1.e+06, 1.e+07, 1.e+08, 1.e+09, 1.e+10]))


Сначала мы проверяем, какое значение \(\alpha\) было выбрано.

model[-1].regressor_.alpha_
np.float64(10.0)

Затем мы проверяем качество прогнозов.

mae_train = median_absolute_error(y_train, model.predict(X_train))
y_pred = model.predict(X_test)
mae_test = median_absolute_error(y_test, y_pred)
scores = {
    "MedAE on training set": f"{mae_train:.2f} $/hour",
    "MedAE on testing set": f"{mae_test:.2f} $/hour",
}

_, ax = plt.subplots(figsize=(5, 5))
display = PredictionErrorDisplay.from_predictions(
    y_test, y_pred, kind="actual_vs_predicted", ax=ax, scatter_kwargs={"alpha": 0.5}
)
ax.set_title("Ridge model, optimum regularization")
for name, score in scores.items():
    ax.plot([], [], " ", label=f"{name}: {score}")
ax.legend(loc="upper left")
plt.tight_layout()
Ridge model, optimum regularization

Способность воспроизвести данные регрессионной модели похожа на способность нерегуляризованной модели.

coefs = pd.DataFrame(
    model[-1].regressor_.coef_,
    columns=["Coefficients importance"],
    index=feature_names,
)
coefs.plot.barh(figsize=(9, 7))
plt.title("Ridge model, with regularization, normalized variables")
plt.xlabel("Raw coefficient values")
plt.axvline(x=0, color=".5")
plt.subplots_adjust(left=0.3)
Ridge model, with regularization, normalized variables

Коэффициенты существенно различаются. Коэффициенты AGE и EXPERIENCE оба положительные, но теперь они оказывают меньшее влияние на прогноз.

Регуляризация уменьшает влияние коррелированных переменных на модель, поскольку вес распределяется между двумя предиктивными переменными, поэтому ни одна из них в отдельности не будет иметь сильного веса.

С другой стороны, веса, полученные с регуляризацией, более стабильны (см. раздел «Руководство пользователя» Регрессия и классификация с гребнем). Это увеличение стабильности видно из графика, полученного из возмущений данных, в перекрестной проверке. Этот график можно сравнить с предыдущим.

cv_model = cross_validate(
    model,
    X,
    y,
    cv=cv,
    return_estimator=True,
    n_jobs=2,
)
coefs = pd.DataFrame(
    [est[-1].regressor_.coef_ for est in cv_model["estimator"]], columns=feature_names
)
plt.ylabel("Age coefficient")
plt.xlabel("Experience coefficient")
plt.grid(True)
plt.xlim(-0.4, 0.5)
plt.ylim(-0.4, 0.5)
plt.scatter(coefs["AGE"], coefs["EXPERIENCE"])
_ = plt.title("Co-variations of coefficients for AGE and EXPERIENCE across folds")
Co-variations of coefficients for AGE and EXPERIENCE across folds

Линейные модели с разреженными коэффициентами

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

Модели Лассо (см. раздел руководства пользователя Lasso) оценивают разреженные коэффициенты. LassoCV применяет перекрестную проверку, чтобы определить, какое значение параметра регуляризации (alpha) лучше всего подходит для оценки модели.

from sklearn.linear_model import LassoCV

alphas = np.logspace(-10, 10, 21)  # alpha values to be chosen from by cross-validation
model = make_pipeline(
    preprocessor,
    TransformedTargetRegressor(
        regressor=LassoCV(alphas=alphas, max_iter=100_000),
        func=np.log10,
        inverse_func=sp.special.exp10,
    ),
)

_ = model.fit(X_train, y_train)

Сначала мы проверяем, какое значение \(\alpha\) было выбрано.

model[-1].regressor_.alpha_
np.float64(0.001)

Затем мы проверяем качество прогнозов.

mae_train = median_absolute_error(y_train, model.predict(X_train))
y_pred = model.predict(X_test)
mae_test = median_absolute_error(y_test, y_pred)
scores = {
    "MedAE on training set": f"{mae_train:.2f} $/hour",
    "MedAE on testing set": f"{mae_test:.2f} $/hour",
}

_, ax = plt.subplots(figsize=(6, 6))
display = PredictionErrorDisplay.from_predictions(
    y_test, y_pred, kind="actual_vs_predicted", ax=ax, scatter_kwargs={"alpha": 0.5}
)
ax.set_title("Lasso model, optimum regularization")
for name, score in scores.items():
    ax.plot([], [], " ", label=f"{name}: {score}")
ax.legend(loc="upper left")
plt.tight_layout()
Lasso model, optimum regularization

Для нашего набора данных модель снова не очень предсказательная.

coefs = pd.DataFrame(
    model[-1].regressor_.coef_,
    columns=["Coefficients importance"],
    index=feature_names,
)
coefs.plot(kind="barh", figsize=(9, 7))
plt.title("Lasso model, optimum regularization, normalized variables")
plt.axvline(x=0, color=".5")
plt.subplots_adjust(left=0.3)
Lasso model, optimum regularization, normalized variables

Модель Лассо определяет корреляцию между AGE и EXPERIENCE и подавляет одну из них ради прогноза.

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

Действительно, мы можем проверить изменчивость коэффициентов по разделам.

cv_model = cross_validate(
    model,
    X,
    y,
    cv=cv,
    return_estimator=True,
    n_jobs=2,
)
coefs = pd.DataFrame(
    [est[-1].regressor_.coef_ for est in cv_model["estimator"]], columns=feature_names
)
plt.figure(figsize=(9, 7))
sns.stripplot(data=coefs, orient="h", palette="dark:k", alpha=0.5)
sns.boxplot(data=coefs, orient="h", color="cyan", saturation=0.5, whis=100)
plt.axvline(x=0, color=".5")
plt.title("Coefficient variability")
plt.subplots_adjust(left=0.3)
Coefficient variability

Мы наблюдаем, что коэффициенты AGE и EXPERIENCE сильно меняются в зависимости от раздела.

Неправильная интерпретация причинно-следственной связи

Создатели политики могут захотеть узнать влияние образования на заработную плату, чтобы оценить, оправдано ли определенное политическое решение, направленное на побуждение людей к получению большего образования. Хотя модели машинного обучения отлично подходят для измерения статистических связей, они, как правило, не могут вывести причинно-следственные эффекты.

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

К сожалению, вероятно, существуют необнаблюдаемые смешивающие переменные, которые либо увеличивают, либо уменьшают этот коэффициент. Смешивающая переменная — это переменная, которая влияет как на ОБРАЗОВАНИЕ, так и на ЗАРАБОТНУЮ ПЛАТУ. Одним примером такой переменной является способность. Предположительно, более способные люди с большей вероятностью получат образование, и при этом с большей вероятностью получат более высокую почасовую заработную плату на любом уровне образования. В этом случае способность вызывает положительное смещение, вызванное пропущенной переменной (OVB) для коэффициента ОБРАЗОВАНИЯ, тем самым преувеличивая влияние образования на заработную плату.

См. Невозможность машинного обучения вывести причинно-следственные эффекты для моделируемого случая смещения OVB, связанного с способностью.

Уроки, извлеченные из опыта

  • Для получения важности признаков коэффициенты необходимо масштабировать до одной и той же единицы измерения. Масштабирование с использованием стандартного отклонения признака является полезным приближением.
  • Интерпретация причинно-следственной связи затруднена при наличии смешивающих эффектов. Если взаимосвязь между двумя переменными также зависит от чего-то незаметного, мы должны быть осторожны при выводах о причинно-следственной связи.
  • Коэффициенты в многофакторных линейных моделях представляют зависимость между заданным признаком и целевой переменной при условии присутствия других признаков.
  • Коррелированные признаки вызывают нестабильность коэффициентов линейных моделей, и их эффекты трудно отделить друг от друга.
  • Разные линейные модели по-разному реагируют на корреляцию признаков, и коэффициенты могут существенно отличаться друг от друга.
  • Проверка коэффициентов по итерациям цикла перекрестной проверки дает представление об их устойчивости.
  • Коэффициенты вряд ли будут иметь какое-либо причинно-следственное значение. Они имеют тенденцию к смещению из-за незамеченных смешивающих факторов.
  • Инструменты проверки могут не обязательно предоставлять информацию о реальном процессе генерации данных.

Общее время выполнения скрипта: (0 минут 14.475 секунд)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_linear_model_coefficient_interpretation.ipynb

Download Python source code: plot_linear_model_coefficient_interpretation.py

Download zipped: plot_linear_model_coefficient_interpretation.zip

Связанные примеры

Коэффициенты регрессии в зависимости от L2-регуляризации

Невозможность машинного обучения вывести причинно-следственные эффекты

График коэффициентов гребня в зависимости от регуляризации

Влияние регуляризации модели на ошибку обучения и тестирования

© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/inspection/plot_linear_model_coefficient_interpretation.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API