哈喽,大家好~
最近,我们发布了200个强大算法模型,从原理到案例,带大家非常通透理解,入门机器学习。
还有核心社群,分享干货内容,大家要一起加入进来~
言归正传!
K近邻回归是一种基于实例的非参数学习方法。
与线性回归或者神经网络不同,它不通过显式训练全局参数来拟合函数,而是在预测时直接参考训练数据中距离目标点最近的样本。
其核心思想可概述为:对于一个新的输入点 ,找到训练集中与 距离最近的 个邻居,使用这些邻居的输出 的加权平均作为预测值。
直观上,KNN回归实现了局部平滑:在局部邻域内,假设函数值变化不大,邻居的平均值应当接近目标点的真实值。
这种简单思想却蕴含丰富的理论与实践细节:距离度量选择、邻居数 的选取、权重函数设计、维度灾难、稀疏数据、多输出扩展、搜索结构、偏差-方差权衡、统一的核回归视角、统计一致性与收敛速率等等。
基本定义与估计器
给定训练样本 ,其中 为特征向量, 为标量回归目标。
目标是学习一个函数 ,用于预测新点 的输出。KNN回归的估计器定义为:
第一步:基于某种距离度量 (常用Minkowski距离),找到集合 ,其包含训练集中与 距离最小的 个点。
第二步:计算加权平均输出作为预测:
其中 是对邻居的权重,一般依赖于距离 。
常见权重方案:
均匀权重:
距离权重:
其中 避免除零。
核权重(更一般):
其中 为核函数, 为带宽(局部尺度),可取为到第 近邻的距离 ,形成自适应带宽。
距离度量常选用Minkowski范数:
特别地:
-
为欧氏距离; -
为曼哈顿距离。
也可采用加权距离或马氏距离:
其中 为协方差矩阵或度量学习得到的正定矩阵。
核回归视角与Nadaraya-Watson等价
KNN回归在核回归框架下可视为一种特殊的局部常数估计器。设 是到第 近邻的距离 ,取核函数 为指标核:
则KNN回归的均匀权重估计器等价于Nadaraya-Watson核回归:
其中 是半径为 的 维球。此时 决定了有效邻域的体积大小,从而控制平滑程度;当 增大时,需要更大的 才能形成稳定估计,这反映了维度灾难。
更一般地,如果采用平滑核(例如高斯核 ),并令 为固定带宽或自适应带宽,则KNN回归成为核回归的一种特例。
权重随距离衰减,使得近邻贡献更大,有助于减少边界效应与提高拟合稳定性。
偏差-方差分析与一致性
设数据生成过程为:
假定 服从密度 , 在局部满足Lipschitz条件或具有有界平方可积性。
我们希望研究KNN估计 的偏差与方差。
邻域半径的缩放关系:
设 是 维单位球体积:
对于大样本 、适度的 (随 增长),到第 近邻的距离 近似满足:
因此有:
直觉:在密度 下,半径 的球内期望包含样本数 。令其等于 ,即可得到 的期望。该尺度关系体现了邻域半径在高维空间随 与 的变化规律。
偏差上界:
若 在 附近满足Lipschitz条件,即存在常数 使得对于 在 邻域内有:
均匀权重的KNN估计器的偏差可界定:
因此,偏差随邻域半径(等价于 )缩小而减小。更大的 意味着更大的邻域,偏差不一定更小(若 非线性明显,过大的邻域会引入偏差);但高维下为了方差稳定可能需要更大 ,形成偏差-方差权衡。
方差分析:
在均匀权重下,假设噪声同方差 (或者在邻域内近似同方差),则:
若采用距离权重 ,以正规化 ,则有:
当权重更集中于最近邻, 增大,方差变大。距离加权在边界与不均匀密度下有助于减少偏差,但会增大方差,需要通过调参(如更大 )补偿。
一致性与收敛:
经典结果指出,在适当条件下(包括 且 、 有界或平方可积、点 为 分布的连续点、用合理的度量与权重):
即KNN回归在该点上一致。
收敛率取决于维度 、光滑性指标以及邻域缩放。粗略地,在Hölder光滑性与均匀密度的情形下,局部常数回归的最优收敛率与核回归相近,受维度 影响显著,体现维度灾难:当 增大,所需样本数呈指数增长。
距离度量、特征缩放与度量学习:
特征缩放至关重要。不同量纲的特征会让距离度量失真,应对数值型特征做标准化或归一化。
对相关特征可考虑马氏距离,以适应协方差结构。
在高维或复杂特征下,可以引入度量学习(如LMNN、ITML)以学习 使得 更符合任务结构。
对分类变量需特别处理:可以使用合适的编码方案(如one-hot后采用 距离,或定义混合型距离度量)。
完整案例
我们构造一个非线性、异方差、含少量异常值的二维特征数据集(并附加一个无关噪声特征,以体现维度/无关特征对KNN的影响),训练KNN回归模型并进行多维度分析。
数据构建思路:
-
特征维度 ,其中 为有用特征, 为无关噪声特征。 -
定义真实函数: -
异方差噪声: 。 -
异常值:随机挑选约2%样本将 加上较大偏移。 -
样本规模: 。 -
训练/测试划分:训练70%,测试30%。
模型与调参:
-
标准化特征(StandardScaler)。 -
使用KNeighborsRegressor,算法选择auto,权重尝试uniform与distance, 选择范围{1, 2, 3}, 选择范围{3, 5, 7, 11, 21, 31, 51}。 -
使用GridSearchCV进行5折交叉验证,以负均方误差为评分。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import seaborn as sns
np.random.seed(42)
# 1) 构造虚拟数据集
n = 2000
# x1, x2 在 [-2, 2] 范围均匀分布,x3 为无关噪声特征
X1 = np.random.uniform(-2, 2, size=n)
X2 = np.random.uniform(-2, 2, size=n)
X3 = np.random.normal(0, 1, size=n) # 无关噪声特征
X = np.column_stack([X1, X2, X3])
# 真实函数
def f_true(x1, x2):
return (np.sin(3*x1) + 0.5*np.cos(5*x2)
+ 0.3*np.sqrt(x1**2 + x2**2)
+ 0.7*np.exp(-2*(x1 - 0.5)**2 - 1.5*(x2 + 0.3)**2))
# 异方差噪声
def sigma(x1, x2):
return0.15 + 0.35 * (x1 > 0) * np.abs(x2)
y_true = f_true(X1, X2)
noise = np.random.normal(0, sigma(X1, X2))
y = y_true + noise
# 注入异常值(2%)
outlier_mask = np.random.rand(n) < 0.02
y[outlier_mask] += np.random.choice([5.0, -5.0], size=np.sum(outlier_mask))
# 训练/测试划分
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state=42
)
# 2) 构建Pipeline并进行GridSearchCV
pipe = Pipeline([
('scaler', StandardScaler()),
('knn', KNeighborsRegressor(algorithm='auto'))
])
param_grid = {
'knn__n_neighbors': [3, 5, 7, 11, 21, 31, 51],
'knn__weights': ['uniform', 'distance'],
'knn__p': [1, 2, 3],
'knn__leaf_size': [30, 60]
}
grid = GridSearchCV(
estimator=pipe,
param_grid=param_grid,
scoring='neg_mean_squared_error',
cv=5,
n_jobs=-1,
verbose=1
)
grid.fit(X_train, y_train)
best_model = grid.best_estimator_
best_params = grid.best_params_
best_score = -grid.best_score_
print("最佳参数:", best_params)
print("CV最优MSE:", best_score)
# 3) 测试集评估
y_pred_test = best_model.predict(X_test)
mse_test = mean_squared_error(y_test, y_pred_test)
mae_test = mean_absolute_error(y_test, y_pred_test)
r2_test = r2_score(y_test, y_pred_test)
print(f"Test MSE={mse_test:.4f}, MAE={mae_test:.4f}, R2={r2_test:.4f}")
# 4) 构造网格以绘制热力图(忽略x3,令其取训练集均值或0)
x1_lin = np.linspace(-2, 2, 200)
x2_lin = np.linspace(-2, 2, 200)
X1g, X2g = np.meshgrid(x1_lin, x2_lin)
X3_fixed = np.zeros_like(X1g) # 固定第三特征为0
X_grid = np.dstack([X1g, X2g, X3_fixed]).reshape(-1, 3)
# 网格上的真实值与预测值
Y_true_grid = f_true(X_grid[:, 0], X_grid[:, 1]).reshape(X1g.shape)
Y_pred_grid = best_model.predict(X_grid).reshape(X1g.shape)
err_grid = (Y_pred_grid - Y_true_grid)
# 5) 为绘制第4子图,统计不同k与weights的CV MSE曲线
# 为了可视化,我们对GridSearch结果进行聚合
results = []
for i in range(len(grid.cv_results_['params'])):
params = grid.cv_results_['params'][i]
mean_mse = -grid.cv_results_['mean_test_score'][i]
results.append((params['knn__weights'], params['knn__n_neighbors'], params['knn__p'], mean_mse))
results = np.array(results, dtype=object)
# 构造两条曲线:p=2时,weights=uniform 与 weights=distance 的MSE随k变化
def aggregate_curve(weight_type, p_val):
mask = (results[:, 0] == weight_type) & (results[:, 2] == p_val)
sub = results[mask]
# 按k排序
order = np.argsort(sub[:, 1].astype(int))
k_vals = sub[order, 1].astype(int)
mse_vals = sub[order, 3].astype(float)
return k_vals, mse_vals
k_u2, mse_u2 = aggregate_curve('uniform', 2)
k_d2, mse_d2 = aggregate_curve('distance', 2)
# 6) 可视化
plt.figure(figsize=(16, 12))
# 图A:真实函数热力图
ax1 = plt.subplot(2, 2, 1)
im1 = ax1.imshow(Y_true_grid, origin='lower', extent=[-2, 2, -2, 2], cmap='plasma', aspect='auto')
ax1.set_title('A. Ground Truth f(x1, x2)', fontsize=14, color='#222222')
ax1.set_xlabel('x1')
ax1.set_ylabel('x2')
cb1 = plt.colorbar(im1, ax=ax1, fraction=0.046, pad=0.04)
cb1.set_label('True Value', color='#222222')
# 图B:KNN预测热力图
ax2 = plt.subplot(2, 2, 2)
im2 = ax2.imshow(Y_pred_grid, origin='lower', extent=[-2, 2, -2, 2], cmap='viridis', aspect='auto')
ax2.set_title('B. KNN Prediction', fontsize=14, color='#222222')
ax2.set_xlabel('x1')
ax2.set_ylabel('x2')
cb2 = plt.colorbar(im2, ax=ax2, fraction=0.046, pad=0.04)
cb2.set_label('Predicted Value', color='#222222')
# 图C:误差热力图(Pred - True)
ax3 = plt.subplot(2, 2, 3)
im3 = ax3.imshow(err_grid, origin='lower', extent=[-2, 2, -2, 2], cmap='magma', aspect='auto')
ax3.set_title('C. Error Map (Prediction - Truth)', fontsize=14, color='#222222')
ax3.set_xlabel('x1')
ax3.set_ylabel('x2')
cb3 = plt.colorbar(im3, ax=ax3, fraction=0.046, pad=0.04)
cb3.set_label('Error', color='#222222')
# 图D:CV MSE随k的变化曲线(p=2)
ax4 = plt.subplot(2, 2, 4)
ax4.plot(k_u2, mse_u2, marker='o', color='#E91E63', label='uniform, p=2', linewidth=2)
ax4.plot(k_d2, mse_d2, marker='s', color='#00BCD4', label='distance, p=2', linewidth=2)
ax4.set_title('D. CV MSE vs k (p=2)', fontsize=14, color='#222222')
ax4.set_xlabel('k (n_neighbors)')
ax4.set_ylabel('Cross-validated MSE')
ax4.grid(True, alpha=0.3)
ax4.legend()
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
# 7) 更多评估指标与分析
print("更多评估指标:")
print(f"- Test MSE: {mse_test:.4f}")
print(f"- Test MAE: {mae_test:.4f}")
print(f"- Test R2: {r2_test:.4f}")
print(f"- GridSearch最佳参数: {best_params}")
真实函数热力图:
显示 空间内的实际函数形状,包括正弦、余弦、径向项与局部高斯峰的组合。目的在于建立基准参考,观察复杂非线性结构与峰谷分布。
KNN预测热力图:
展示模型在同一空间的预测值分布。对比A与B可以直观看出KNN对复杂非线性的拟合能力。一般而言,KNN对局部结构拟合较好,但在数据稀疏或噪声较大区域可能出现过度平滑或者局部抖动现象。
误差热力图:
展示预测误差的空间分布。该图对诊断模型的弱点至关重要。通常会观察到在数据密度较低区域(例如边缘)或噪声方差较高区域,误差较大。此外,在函数变化剧烈的区域(例如高斯峰边缘或正弦/余弦急剧变化处),局部常数估计的偏差更显著。
CV MSE vs k:
表明模型复杂度(由 控制)与泛化误差的关系。曲线一般呈现U型或缓慢下降-上升的趋势:小 表现为高方差,较大的 实现更稳健但可能引入偏差。对比uniform与distance权重曲线,可以观察到距离加权在某些 范围内或整体上提供更低的MSE(依赖数据结构)。
综上所有的内容,K近邻回归是一个直观、易用且理论基础扎实的非参数回归方法。
通过在局部邻域内对输出进行加权平均实现复杂函数的拟合,具有良好的可解释性与统计一致性。
在合适的距离度量、权重方案与参数选择下,它可以对非线性、异方差数据提供高质量拟合。
然而,其性能与效率高度依赖于特征尺度、维度与数据分布,并且在海量数据与高维场景下面临挑战。通过特征工程、降维、度量学习、鲁棒处理与近似近邻技术,可以显著扩展KNN的适用性与表现。
最后

