Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.
Сравнение гребневой регрессии с ядром и регрессии с помощью гауссовских процессов
В этом примере показаны различия между гребневой регрессией с ядром и регрессией с помощью гауссовских процессов.
В гребневой регрессии с ядром и регрессии с помощью гауссовских процессов используется так называемый «ядерный трюк», позволяющий сделать модели достаточно выразительными для аппроксимации обучающих данных. Однако задачи машинного обучения, решаемые этими двумя методами, существенно различаются.
Гребневая регрессия с ядром находит целевую функцию, минимизирующую функцию потерь (среднеквадратичную ошибку).
Вместо поиска одной целевой функции регрессия с помощью гауссовских процессов использует вероятностный подход: на основе теоремы Байеса задаётся апостериорное гауссовское распределение целевых функций. Таким образом, априорные вероятности целевых функций объединяются с функцией правдоподобия, определённой по наблюдаемым обучающим данным, чтобы получить оценки апостериорных распределений.
Мы проиллюстрируем эти различия на примере, а также рассмотрим настройку гиперпараметров ядра.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Создание набора данных
Мы создадим синтетический набор данных. Истинный процесс генерации данных принимает одномерный вектор и вычисляет его синус. Обратите внимание, что период этой синусоиды равен \(2 \pi\). Позже в этом примере мы воспользуемся этой информацией.
import numpy as np rng = np.random.RandomState(0) data = np.linspace(0, 30, num=1_000).reshape(-1, 1) target = np.sin(data).ravel()
Теперь представим, что мы получаем наблюдения от этого истинного процесса. Однако добавим несколько усложнений:
- измерения будут зашумлёнными;
- будут доступны только отсчёты из начала сигнала.
training_sample_indices = rng.choice(np.arange(0, 400), size=40, replace=False)
training_data = data[training_sample_indices]
training_noisy_target = target[training_sample_indices] + 0.5 * rng.randn(
len(training_sample_indices)
)
Построим график истинного сигнала и зашумлённых измерений, доступных для обучения.
import matplotlib.pyplot as plt
plt.plot(data, target, label="True signal", linewidth=2)
plt.scatter(
training_data,
training_noisy_target,
color="black",
label="Noisy measurements",
)
plt.legend()
plt.xlabel("data")
plt.ylabel("target")
_ = plt.title(
"Illustration of the true generative process and \n"
"noisy measurements available during training"
)

Ограничения простой линейной модели
Сначала рассмотрим ограничения линейной модели на нашем наборе данных. Обучим Ridge и проверим предсказания этой модели на нашем наборе данных.
from sklearn.linear_model import Ridge
ridge = Ridge().fit(training_data, training_noisy_target)
plt.plot(data, target, label="True signal", linewidth=2)
plt.scatter(
training_data,
training_noisy_target,
color="black",
label="Noisy measurements",
)
plt.plot(data, ridge.predict(data), label="Ridge regression")
plt.legend()
plt.xlabel("data")
plt.ylabel("target")
_ = plt.title("Limitation of a linear model such as ridge")

Такая гребневая регрессия недообучается, поскольку её выразительности недостаточно.
Ядерные методы: гребневая регрессия с ядром и гауссовский процесс
Гребневая регрессия с ядром
Сделать предыдущую линейную модель более выразительной можно с помощью так называемого ядра. Ядро — это отображение исходного пространства признаков в другое пространство. Проще говоря, оно используется для преобразования исходных данных в новое, более сложное пространство признаков. Это новое пространство явно определяется выбором ядра.
В нашем случае мы знаем, что истинный процесс генерации данных является периодической функцией. Можно использовать ядро ExpSineSquared, которое позволяет восстановить периодичность. Класс KernelRidge принимает такое ядро.
Использование этой модели вместе с ядром эквивалентно преобразованию данных с помощью функции отображения ядра с последующим применением гребневой регрессии. На практике данные не преобразуются явно; вместо этого скалярное произведение между объектами в пространстве признаков большей размерности вычисляется с помощью «ядерного трюка».
Итак, воспользуемся такой моделью KernelRidge.
import time
from sklearn.gaussian_process.kernels import ExpSineSquared
from sklearn.kernel_ridge import KernelRidge
kernel_ridge = KernelRidge(kernel=ExpSineSquared())
start_time = time.time()
kernel_ridge.fit(training_data, training_noisy_target)
print(
f"Fitting KernelRidge with default kernel: {time.time() - start_time:.3f} seconds"
)
Fitting KernelRidge with default kernel: 0.001 seconds
plt.plot(data, target, label="True signal", linewidth=2, linestyle="dashed")
plt.scatter(
training_data,
training_noisy_target,
color="black",
label="Noisy measurements",
)
plt.plot(
data,
kernel_ridge.predict(data),
label="Kernel ridge",
linewidth=2,
linestyle="dashdot",
)
plt.legend(loc="lower right")
plt.xlabel("data")
plt.ylabel("target")
_ = plt.title(
"Kernel ridge regression with an exponential sine squared\n "
"kernel using default hyperparameters"
)

Эта обученная модель недостаточно точна. Дело в том, что мы не задали параметры ядра, а использовали значения по умолчанию. Давайте посмотрим на них.
kernel_ridge.kernel
ExpSineSquared(length_scale=1, periodicity=1)
У нашего ядра два параметра: масштаб длины и периодичность. Для нашего набора данных мы используем sin как процесс генерации данных, что подразумевает периодичность сигнала \(2 \pi\). Значение параметра по умолчанию равно \(1\), что объясняет высокую частоту в предсказаниях модели. Аналогичные выводы можно сделать и для параметра масштаба длины. Таким образом, параметры ядра необходимо настроить. Для настройки различных параметров модели гребневой регрессии с ядром мы воспользуемся случайным поиском: будем настраивать параметр alpha и параметры ядра.
from scipy.stats import loguniform
from sklearn.model_selection import RandomizedSearchCV
param_distributions = {
"alpha": loguniform(1e0, 1e3),
"kernel__length_scale": loguniform(1e-2, 1e2),
"kernel__periodicity": loguniform(1e0, 1e1),
}
kernel_ridge_tuned = RandomizedSearchCV(
kernel_ridge,
param_distributions=param_distributions,
n_iter=500,
random_state=0,
)
start_time = time.time()
kernel_ridge_tuned.fit(training_data, training_noisy_target)
print(f"Time for KernelRidge fitting: {time.time() - start_time:.3f} seconds")
Time for KernelRidge fitting: 4.956 seconds
Обучение модели теперь требует больше вычислений, поскольку нужно проверить несколько комбинаций гиперпараметров. Чтобы получить представление о результатах, посмотрим на найденные гиперпараметры.
kernel_ridge_tuned.best_params_
{'alpha': np.float64(1.991584977345022), 'kernel__length_scale': np.float64(0.7986499491396734), 'kernel__periodicity': np.float64(6.6072758064261095)}
Судя по лучшим параметрам, они отличаются от значений по умолчанию. Кроме того, периодичность ближе к ожидаемому значению: \(2 \pi\). Теперь посмотрим на предсказания настроенной модели гребневой регрессии с ядром.
start_time = time.time()
predictions_kr = kernel_ridge_tuned.predict(data)
print(f"Time for KernelRidge predict: {time.time() - start_time:.3f} seconds")
Time for KernelRidge predict: 0.002 seconds
plt.plot(data, target, label="True signal", linewidth=2, linestyle="dashed")
plt.scatter(
training_data,
training_noisy_target,
color="black",
label="Noisy measurements",
)
plt.plot(
data,
predictions_kr,
label="Kernel ridge",
linewidth=2,
linestyle="dashdot",
)
plt.legend(loc="lower right")
plt.xlabel("data")
plt.ylabel("target")
_ = plt.title(
"Kernel ridge regression with an exponential sine squared\n "
"kernel using tuned hyperparameters"
)

Модель стала гораздо точнее. Мы всё ещё наблюдаем некоторые ошибки, в основном вызванные шумом, добавленным в набор данных.
Регрессия с помощью гауссовских процессов
Теперь обучим GaussianProcessRegressor на том же наборе данных. При обучении гауссовского процесса гиперпараметры ядра оптимизируются в процессе обучения. Внешний поиск гиперпараметров не требуется. Здесь мы создадим ядро немного сложнее, чем для гребневой регрессии с ядром: добавим WhiteKernel, которое используется для оценки шума в наборе данных.
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import WhiteKernel
kernel = 1.0 * ExpSineSquared(1.0, 5.0, periodicity_bounds=(1e-2, 1e1)) + WhiteKernel(
1e-1
)
gaussian_process = GaussianProcessRegressor(kernel=kernel)
start_time = time.time()
gaussian_process.fit(training_data, training_noisy_target)
print(
f"Time for GaussianProcessRegressor fitting: {time.time() - start_time:.3f} seconds"
)
Time for GaussianProcessRegressor fitting: 0.039 seconds
Обучение гауссовского процесса обходится гораздо дешевле, чем обучение гребневой регрессии с ядром со случайным поиском. Проверим параметры вычисленных ядер.
gaussian_process.kernel_
0.675**2 * ExpSineSquared(length_scale=1.34, periodicity=6.57) + WhiteKernel(noise_level=0.182)
Как и ожидалось, параметры были оптимизированы. Рассмотрев параметр periodicity, мы видим, что найденный период близок к теоретическому значению \(2 \pi\). Теперь посмотрим на предсказания модели.
start_time = time.time()
mean_predictions_gpr, std_predictions_gpr = gaussian_process.predict(
data,
return_std=True,
)
print(
f"Time for GaussianProcessRegressor predict: {time.time() - start_time:.3f} seconds"
)
Time for GaussianProcessRegressor predict: 0.003 seconds
plt.plot(data, target, label="True signal", linewidth=2, linestyle="dashed")
plt.scatter(
training_data,
training_noisy_target,
color="black",
label="Noisy measurements",
)
# Plot the predictions of the kernel ridge
plt.plot(
data,
predictions_kr,
label="Kernel ridge",
linewidth=2,
linestyle="dashdot",
)
# Plot the predictions of the gaussian process regressor
plt.plot(
data,
mean_predictions_gpr,
label="Gaussian process regressor",
linewidth=2,
linestyle="dotted",
)
plt.fill_between(
data.ravel(),
mean_predictions_gpr - std_predictions_gpr,
mean_predictions_gpr + std_predictions_gpr,
color="tab:green",
alpha=0.2,
)
plt.legend(loc="lower right")
plt.xlabel("data")
plt.ylabel("target")
_ = plt.title("Comparison between kernel ridge and gaussian process regressor")

Мы видим, что результаты гребневой регрессии с ядром и регрессора на основе гауссовского процесса близки. Однако регрессор на основе гауссовского процесса также предоставляет информацию о неопределённости, недоступную для гребневой регрессии с ядром. Благодаря вероятностной формулировке целевых функций гауссовский процесс может выдавать стандартное отклонение (или ковариацию) вместе со средними предсказаниями целевых функций.
Однако за это приходится платить: вычисление предсказаний с помощью гауссовского процесса занимает больше времени.
Итоговый вывод
В завершение рассмотрим способность обеих моделей экстраполировать. В качестве обучающего набора мы использовали только начало сигнала. Периодическое ядро заставляет модель повторять закономерность, обнаруженную в обучающем наборе. Благодаря этому свойству ядра и способности обеих моделей экстраполировать мы видим, что модели продолжают предсказывать синусоидальный сигнал.
Гауссовский процесс позволяет объединять ядра. Поэтому можно объединить ядро экспоненциального синуса в квадрате с ядром радиальной базисной функции.
from sklearn.gaussian_process.kernels import RBF
kernel = 1.0 * ExpSineSquared(1.0, 5.0, periodicity_bounds=(1e-2, 1e1)) * RBF(
length_scale=15, length_scale_bounds="fixed"
) + WhiteKernel(1e-1)
gaussian_process = GaussianProcessRegressor(kernel=kernel)
gaussian_process.fit(training_data, training_noisy_target)
mean_predictions_gpr, std_predictions_gpr = gaussian_process.predict(
data,
return_std=True,
)
plt.plot(data, target, label="True signal", linewidth=2, linestyle="dashed")
plt.scatter(
training_data,
training_noisy_target,
color="black",
label="Noisy measurements",
)
# Plot the predictions of the kernel ridge
plt.plot(
data,
predictions_kr,
label="Kernel ridge",
linewidth=2,
linestyle="dashdot",
)
# Plot the predictions of the gaussian process regressor
plt.plot(
data,
mean_predictions_gpr,
label="Gaussian process regressor",
linewidth=2,
linestyle="dotted",
)
plt.fill_between(
data.ravel(),
mean_predictions_gpr - std_predictions_gpr,
mean_predictions_gpr + std_predictions_gpr,
color="tab:green",
alpha=0.2,
)
plt.legend(loc="lower right")
plt.xlabel("data")
plt.ylabel("target")
_ = plt.title("Effect of using a radial basis function kernel")

Использование ядра радиальной базисной функции ослабляет эффект периодичности, когда в обучающем наборе больше нет отсчётов. По мере удаления тестовых отсчётов от обучающих предсказания сходятся к среднему значению, а стандартное отклонение также увеличивается.
Общее время выполнения скрипта: (0 минут 5.750 секунд)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/gaussian_process/plot_compare_gpr_krr.html