首页
/ TheAlgorithms/Python 中局部加权线性回归(LWLR):从高斯加权代价函数到 NumPy 闭式解实现

TheAlgorithms/Python 中局部加权线性回归(LWLR):从高斯加权代价函数到 NumPy 闭式解实现

2026-09-04 22:21:50作者:翟江哲Frasier

局部加权线性回归(Locally Weighted Linear Regression,LWLR,又称 Local Regression)是一种非参数机器学习算法,它不依赖一组全局固定的模型参数,而是针对每个查询点单独求解局部回归系数。本文以仓库中 machine_learning/local_weighted_learning/README.md 的理论讲解为主线,结合 local_weighted_learning.py 的 NumPy 实现,完整覆盖术语定义、加权代价函数推导、数值算例、算法步骤,以及源码级实现细节与运行方式。

与普通线性回归的对比:问题从何而来

普通线性回归是一种监督学习算法,用于计算输入(X)与输出(Y)之间的线性关系。根据 README 的术语约定:

  • number_of_features(i):特征数量;
  • number_of_training_examples(m):训练样本数量;
  • output_sequence(y):输出序列;
  • θTx\theta^T x:预测点;
  • J(θ)J(\theta):代价函数。

普通线性回归的流程分两步:

  1. 训练阶段:计算 θ\theta 以最小化代价

J(θ)=i=1m(θTxiyi)2J(\theta) = \sum_{i=1}^{m} \left(\theta^T x^i - y^i\right)^2

  1. 预测阶段:对给定查询点 x,返回 θTx\theta^T x

这种"先训练一次、再处处复用同一组 θ\theta"的机制,隐含了一个前提假设:数据点之间存在全局线性关系。一旦 x 与 y 之间的关系是非线性的(README 中给出了非线性数据的示意图场景),固定的全局参数就无法贴合局部弯曲趋势。此时需要非参数算法:不基于固定参数集做预测,而是让参数 θ\theta 针对每个查询点 x 单独计算

这就是 LWLR 的核心思想:在计算 θ\theta 时,对离查询点 x 更近的样本赋予更高权重,离得更远的样本权重更低。

加权代价函数与高斯权重

将权重 wiw^i 引入代价函数后得到 LWLR 的目标:

J(θ)=i=1mwi(θTxiyi)2J(\theta) = \sum_{i=1}^{m} w^i \left(\theta^T x^i - y^i\right)^2

其中:

  • wiw^i 是训练点 xix^i 的非负权重;
  • 离查询点 x 近的 xix^iwiw^i 较大;
  • 离查询点 x 远的 xix^iwiw^i 较小。

一个典型的高斯权重函数为:

wi=exp((xix)(xix)T2τ2)w^i = \exp\left(-\frac{(x^i - x)(x^i - x)^T}{2\tau^2}\right)

其中 τ\tau带宽(bandwidth)参数,控制 wiw^i 随与 x 距离增长的衰减速率。可以把它想象成一条以预测点为中心的钟形曲线:样本离中心越远,权重越低;τ\tau 控制钟形曲线的宽度——τ\tau 越大,曲线越宽,远处样本仍保留较高权重,拟合结果越"平滑";τ\tau 越小,权重衰减越快,拟合越贴近近邻点、越容易受局部噪声影响。

README 中的数值算例(复算验证)

README 给出的算例:查询点 x=5.0x = 5.0,训练点 x1=4.9x^1 = 4.9,另一点按 README 的代入过程 (35)2(3-5)^2 取值为 3.0(README 正文将其写作 x2=5.0x^2 = 5.0,与其后代入的 3 不符,属于原文笔误,下文以代入值 3.0 为准),带宽 τ=0.5\tau = 0.5

  • w1=exp((4.95)22(0.5)2)0.9802w^1 = \exp\left(-\frac{(4.9-5)^2}{2(0.5)^2}\right) \approx 0.9802
  • w2=exp((35)22(0.5)2)0.000335w^2 = \exp\left(-\frac{(3-5)^2}{2(0.5)^2}\right) \approx 0.000335

于是代价函数变为:

J(θ)=0.9802(θTx1y1)2+0.000335(θTx2y2)2J(\theta) = 0.9802\left(\theta^T x^1 - y^1\right)^2 + 0.000335\left(\theta^T x^2 - y^2\right)^2

结论与 README 一致:权重随 x 与 xix^i 间距离的增大指数级下降xix^i 的预测误差对总代价的贡献也随之指数级下降。上面的两个数值已用 NumPy 实际复算,与 README 给出的 0.9802 / 0.000335 完全吻合。

LWLR 的完整算法步骤

继承 README 的描述,LWLR 的每一步都发生在预测时刻

  1. 对每个查询点 x,计算权重 wiw^i
  2. 计算 θ\theta 以最小化加权代价 J(θ)=i=1mwi(θTxiyi)2J(\theta) = \sum_{i=1}^{m} w^i \left(\theta^T x^i - y^i\right)^2
  3. 预测输出:对查询点 x 返回 θTx\theta^T x

与普通回归"训练一次、预测多次"不同,LWLR 每次预测都要对全部 m 个训练点求解一次加权最小二乘,因此它是典型的时间换拟合灵活性的非参数方法。

源码实现:NumPy 闭式解与函数结构

local_weighted_learning.py 将上述理论落地为四个核心函数(weight_matrixlocal_weightlocal_weight_regressionload_data/plot_preds),文件头部 docstring 直接给出了加权最小二乘的闭式解:

β=(XTWX)1(XTWy)\beta = (X^T W X)^{-1}(X^T W y)

其中 X 是设计矩阵,y 是响应向量,W 是对角权重矩阵,权重采用与 README 相同的高斯形式 wi=exp(xix2/2τ2)w_i = \exp(-\|x_i - x\|^2 / 2\tau^2)x\|x\| 为欧几里得范数(2-范数)。源码还特别注明:LOESS(Locally Estimated Scatterplot Smoothing)等其它局部加权回归通常使用不同的权重函数,本实现选用的是高斯权重。

weight_matrix:构造查询点周围的权重矩阵

local_weighted_learning.py#L38-L67

def weight_matrix(point: np.ndarray, x_train: np.ndarray, tau: float) -> np.ndarray:
    m = len(x_train)  # Number of training samples
    weights = np.eye(m)  # Initialize weights as identity matrix
    for j in range(m):
        diff = point - x_train[j]
        weights[j, j] = np.exp(diff @ diff.T / (-2.0 * tau**2))
    return weights
  • 输入 point 是预测点,x_train 是训练 x 值矩阵,tau 为带宽;
  • 从源码结构看,实现用 np.eye(m) 初始化后仅填充对角线元素,这正是 WW对角权重矩阵的体现:wiw^i 只乘在 xix^i 自身的残差平方上,样本之间不产生交叉权重项;
  • 注意当 xix^i 远离预测点时,exp(大数)\exp(-\text{大数}) 会下溢为 0(如 doctest 中距离较远的样本权重直接为 0.0,最近的样本权重也只有 1.4e-207 量级),这与"权重指数级衰减"的理论预期一致。

local_weight:对单个查询点求解局部系数 β

local_weighted_learning.py#L70-L99

def local_weight(point, x_train, y_train, tau):
    weight_mat = weight_matrix(point, x_train, tau)
    weight = np.linalg.inv(x_train.T @ weight_mat @ x_train) @ (
        x_train.T @ weight_mat @ y_train.T
    )
    return weight

两行核心运算精确对应闭式解 β=(XTWX)1(XTWy)\beta = (X^T W X)^{-1}(X^T W y)x_train.T @ weight_mat @ x_trainXTWXX^T W X,对其求逆后再乘 XTWyX^T W y。函数名沿用了文档中"局部权重(local weights)"的叫法,返回的向量就是针对该查询点的局部 β\beta(文档中的 θ\theta)。

local_weight_regression:对每个点做"即查即算"的预测

local_weighted_learning.py#L102-L127

def local_weight_regression(x_train, y_train, tau):
    y_pred = np.zeros(len(x_train))  # Initialize array of predictions
    for i, item in enumerate(x_train):
        y_pred[i] = np.dot(item, local_weight(item, x_train, y_train, tau)).item()
    return y_pred

这个循环正是 LWLR 步骤"对给定查询点 x 返回 θTx\theta^T x"的代码化:对训练集中的每一个点依次当作查询点,重新计算局部 β\beta,再做内积 np.dot(item, β) 得到预测值。doctest 用 3 个 2 维样本验证了这一点:输入 [[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]] 与响应 [[1.01, 1.66, 3.5]]τ=0.6\tau = 0.6 时,输出 array([1.07173261, 1.65970737, 3.50160179])

数据加载与可视化

load_datalocal_weighted_learning.py#L130-L148)从 seaborn 内置数据集读取数据,并通过 np.column_stack((one, x_data)) 在特征矩阵最左列拼接一列全 1——即截距项,保证局部模型形如 β0+β1x,这也解释了为何 doctest 中预测值能平滑地穿过样本附近而非过原点。plot_predslocal_weighted_learning.py#L151-L174)按 x 排序后绘制散点加黄色粗线,展示"局部线性曲线"随数据走势弯曲的非参数特性。

运行方式与依赖

文件底部以 __main__ 块先跑 doctest,再用 seaborn 的 tips 数据集做完整演示(local_weighted_learning.py#L177-L185):

if __name__ == "__main__":
    import doctest
    doctest.testmod()

    # Demo with a dataset from the seaborn module
    training_data_x, total_bill, tip = load_data("tips", "total_bill", "tip")
    predictions = local_weight_regression(training_data_x, tip, 5)
    plot_preds(training_data_x, predictions, total_bill, tip, "total_bill", "tip")

运行前提与注意事项:

  • pyproject.toml 声明 requires-python = ">=3.14",且 numpymatplotlibpandas 均在其 dependencies 中;
  • 需要额外说明的是,load_data 在函数内部 import seaborn as sns,而 seaborn 并未出现在 pyproject.toml 的依赖列表中,因此运行该演示前需自行确保环境装有 seaborn,否则 load_data("tips", ...) 会抛出 ImportError;
  • 演示中带宽取 τ=5\tau = 5,这是一个相对较大的带宽:对 total_bill 量级为 10~50 的数据而言,远处样本仍保有可观权重,画出的局部回归线相对平滑;若把 tau 调小(如 doctest 所用的 0.6),权重衰减极快,曲线会更贴近近邻点。调整 tau 是观察"带宽—平滑度"权衡最直接的实验;
  • 文件头部的 doctest(weight_matrixlocal_weightlocal_weight_regression)随 doctest.testmod() 在模块导入测试(pyproject.toml 中 pytest 配置了 --doctest-modules)时自动执行,可作为对闭式解实现的快速正确性校验;
  • 该模块在 DIRECTORY.md 的目录索引中登记为 "Local Weighted Learning",入口即 machine_learning/local_weighted_learning/local_weighted_learning.py

复杂度与适用边界

从源码结构看,local_weight_regression 对每个查询点调用一次 local_weight,后者需做 m×mm \times m 权重矩阵构造与一次矩阵求逆,因此单个查询点的计算开销随训练集规模近似为 O(m3)O(m^3)(求逆主导),整体预测为 O(m4)O(m^4) 量级——这正是非参数局部回归"存储所有训练数据、每次查询现场求解"的固有代价。它的适用场景因而是中小规模、关系不明或明显非线性、且不需要持久化模型参数的探索性回归任务;反之,若数据量大、需要快速批量预测或可部署的固定模型,普通线性回归或显式基函数扩展通常更合适。

小结

本文对应的仓库材料可归纳为:理论文档 machine_learning/local_weighted_learning/README.md 给出了术语、加权代价函数、高斯权重公式与手算数值例;实现文件 machine_learning/local_weighted_learning/local_weighted_learning.py 用 NumPy 实现了 (XTWX)1(XTWy)(X^T W X)^{-1}(X^T W y) 闭式解、对角权重矩阵构造、逐点预测循环,以及基于 seaborn tips 数据集的可视化演示。读懂这两个文件,即可完整掌握 LWLR "按距离指数加权、逐查询点求解局部线性模型"的算法全貌,并能通过调节带宽 τ\tau 直观理解非参数回归中平滑度与局部贴合度的权衡。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341