4 核回归
看模型的范式 | 核回归 |
---|---|
问题是什么 | 回归问题 |
模型是什么 | 线性模型 |
优化指标 | 间隔最大化(最大化支持面之间的距离) |
求解方法 | (序列最小最优化算法)smo |
评价模型 |
核回归是一种非参数回归方法,不需要事先假设回归函数的具体形式,核回归是放弃寻找一个全局模型(比如线性回归),转而相信“物以类聚”:要预测一个点$ x$ 的值,我们应该更多地参考那些在 $x$ 附近点的值,而远离$ x$ 的点则参考价值较小。通过局部加权平均来拟合复杂的非线性关系。
核心思想:(局部拟合)
-
通过核函数实现“局部加权平均”
-
对每一个待预测点$x_0$,仅利用其邻近的样本点计算预测值$\hat m(x_0)$
-
邻近点的权重由核函数决定,靠近预测点的样本点权重大,远离预测点的样本点权重小
核函数 $K(u)$ 是一个对称、非负的函数,且通常满足积分为1(即它是一个概率密度函数)。它定义了权重如何随着距离 $u$ 的变化而变化,常见的核函数有:
高斯核: 最常用的核之一,权重随距离呈指数衰减 \(K(u)=\frac{1}{\sqrt{2\pi}}\exp(-\frac{1}{2}u^2)\) Epanechnikov 核: 在均方误差意义下是最优的,计算效率高 \(K(u)=\frac{3}{4}(1-u^2)\quad\text{for}\quad|u|\leq1\) 均匀核: 在窗口内的点权重相同,窗口外的点权重为0 \(K(u)=\frac{1}{2}\quad\text{for}\quad|u|\leq1\) 核回归,特别是最常用的Nadaraya-Watson 核回归,假设我们有一组数据点 $(x_1, y_1), (x_2, y_2), …, (x_n, y_n)$。我们想要预测在任意一点 $x$ 对应的 $y$ 值(即 $m(x)$)。核回归给出的估计值是: \(\hat{m}_h(x)=\frac{\sum_{i=1}^nK_h(x-x_i)y_i}{\sum_{i=1}^nK_h(x-x_i)}\)
- $\hat{m}_h(x)$: 在点 $x$ 处的预测值。
- $K_h(\cdot)$: 这就是核函数,它是整个方法的灵魂。
- $h$ 是一个超参数,叫做带宽 或窗宽。它控制了“邻居”的范围有多大。
- $x - x_i$: 表示预测点 $x$ 与样本点 $x_i$ 之间的距离。
其中,带宽 $h$ 是核回归中最重要的超参数,它直接决定了模型的复杂度和性能。如果h 过大(过平滑),会忽略数据中的细节(欠拟合),偏差大。如果h 过小(欠平滑),估计的曲线会紧紧跟随训练数据,甚至穿过每一个点,导致波动非常大(过拟合),方差大。选择带宽 $h$ 的过程就是在偏差和方差之间做一个权衡,通常使用交叉验证的方法来寻找最优的 $h$
参考:
[统计学基础(核回归) | SIRLIS](https://sirlis.cn/posts/statistics-kernel-regression/) |
Kernel Regression 核回归 详细讲解 - 编程宝典
Kernel Regression Advanced Methods for Data Analysis(36-402/36-608) Spring 2014
代码示例:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 设置随机种子以确保结果可重现
np.random.seed(42)
# 1. 生成示例数据
def generate_data(n_samples=300):
X = np.linspace(0, 10, n_samples)
# 创建非线性关系并添加噪声
y = np.sin(X) + 0.2 * X + np.random.normal(0, 0.3, n_samples)
return X, y
X, y = generate_data()
# 将数据划分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 2. 定义核函数
def gaussian_kernel(u, h):
"""高斯核函数"""
return np.exp(-(u**2) / (2 * h**2)) / (h * np.sqrt(2 * np.pi))
def epanechnikov_kernel(u, h):
"""Epanechnikov 核函数"""
u_normalized = np.abs(u) / h
return np.where(u_normalized <= 1, 0.75 * (1 - u_normalized**2), 0)
# 3. 实现核回归
def kernel_regression(X_train, y_train, X_test, h, kernel='gaussian'):
"""
核回归实现
参数:
X_train: 训练特征
y_train: 训练目标
X_test: 测试特征
h: 带宽参数
kernel: 核函数类型 ('gaussian' 或 'epanechnikov')
返回:
y_pred: 预测值
"""
y_pred = np.zeros_like(X_test)
for i, x in enumerate(X_test):
# 计算权重
if kernel == 'gaussian':
weights = gaussian_kernel(X_train - x, h)
elif kernel == 'epanechnikov':
weights = epanechnikov_kernel(X_train - x, h)
else:
raise ValueError("不支持的核函数类型")
# 计算加权平均值
weighted_sum = np.sum(weights * y_train)
sum_weights = np.sum(weights)
# 避免除以零
if sum_weights > 1e-10:
y_pred[i] = weighted_sum / sum_weights
else:
y_pred[i] = np.mean(y_train) # 回退到全局均值
return y_pred
# 4. 使用不同带宽进行预测并评估
bandwidths = [0.1, 0.5, 1.0, 2.0]
mse_scores = []
plt.figure(figsize=(15, 10))
for i, h in enumerate(bandwidths):
# 使用高斯核进行预测
y_pred = kernel_regression(X_train, y_train, X_test, h, kernel='gaussian')
mse = mean_squared_error(y_test, y_pred)
mse_scores.append(mse)
# 为了绘制平滑曲线,在密集点上进行预测
X_plot = np.linspace(0, 10, 300)
y_plot = kernel_regression(X_train, y_train, X_plot, h, kernel='gaussian')
# 绘制子图
plt.subplot(2, 2, i+1)
plt.scatter(X_train, y_train, alpha=0.5, label='训练数据', color='lightgray')
plt.scatter(X_test, y_test, alpha=0.7, label='测试数据', color='blue')
plt.plot(X_plot, y_plot, color='red', linewidth=2, label=f'核回归 (h={h})')
plt.title(f'带宽 h={h}, MSE={mse:.4f}')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.tight_layout()
plt.show()
# 5. 比较不同核函数
plt.figure(figsize=(12, 5))
# 使用最佳带宽
best_h = bandwidths[np.argmin(mse_scores)]
# 高斯核
X_plot = np.linspace(0, 10, 300)
y_gaussian = kernel_regression(X_train, y_train, X_plot, best_h, kernel='gaussian')
# Epanechnikov核
y_epanechnikov = kernel_regression(X_train, y_train, X_plot, best_h, kernel='epanechnikov')
plt.subplot(1, 2, 1)
plt.scatter(X_train, y_train, alpha=0.5, label='训练数据', color='lightgray')
plt.plot(X_plot, y_gaussian, color='red', linewidth=2, label='高斯核')
plt.title(f'高斯核回归 (h={best_h})')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.subplot(1, 2, 2)
plt.scatter(X_train, y_train, alpha=0.5, label='训练数据', color='lightgray')
plt.plot(X_plot, y_epanechnikov, color='green', linewidth=2, label='Epanechnikov核')
plt.title(f'Epanechnikov核回归 (h={best_h})')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.tight_layout()
plt.show()
# 6. 展示带宽选择对模型复杂度的影响
print("不同带宽的MSE得分:")
for h, score in zip(bandwidths, mse_scores):
print(f"带宽 h={h}: MSE = {score:.4f}")