4. 一维RBF插值:从数据到可视化

好,我们正式开始动手了。这一章我会带你走完一维RBF插值的完整流程——从生成原始数据点,到实现高斯基函数,再到求解权重,最后把结果画出来。说白了,就是让你亲眼看到RBF是怎么工作的。

我个人习惯把这类问题拆成四个步骤:造数据 → 选基函数 → 算权重 → 看效果。每一步都不复杂,但每一步都有坑。咱们一个一个来。

4.1 一维数据点生成

先造一组测试数据。我选一个简单的非线性函数:

import numpy as np

# 真实函数:f(x) = sin(2πx) + 0.5 * x
def true_func(x):
    return np.sin(2 * np.pi * x) + 0.5 * x

# 生成训练点:在 [0, 1] 区间均匀取 10 个点
np.random.seed(42)
n_train = 10
x_train = np.linspace(0, 1, n_train)
y_train = true_func(x_train)

# 生成测试点:密集采样用于评估插值效果
x_test = np.linspace(0, 1, 200)
y_test = true_func(x_test)

这里我用了 np.linspace 均匀采样。为什么不用随机点?嗯,一维演示时均匀点能更清楚地展示插值行为。实际项目中,如果你知道数据分布不均匀,那就要考虑用聚类或自适应采样了——这个我们后面章节会讲。

小提示:训练点数量 n_train 不要太大,10~15 个点足够看到 RBF 的效果。点太多反而会让权重矩阵变得病态,后面你会体会到。

4.2 高斯基函数实现

高斯基函数长这样:

φ(r) = exp(-r² / (2σ²))

其中 r = ||x - c|| 是输入点到中心点的距离,σ 是形状参数(也叫宽度参数)。

代码实现很简单:

def gaussian_rbf(r, sigma=1.0):
    """高斯基函数,sigma 控制径向影响范围"""
    return np.exp(-r**2 / (2 * sigma**2))

这里有个关键问题:sigma 怎么选?

我在项目中踩过这个坑。sigma 太小,基函数只影响中心点附近一小块区域,插值结果会剧烈振荡;sigma 太大,所有基函数几乎一样,插值结果变成一条平滑的直线。说白了,sigma 就是 RBF 的「视野半径」。

sigma 取值 效果 典型问题
过小(< 0.05) 每个基函数只影响自身附近 插值曲线振荡剧烈,过拟合
适中(0.1 ~ 0.3) 基函数重叠合理 插值平滑且准确
过大(> 1.0) 所有基函数几乎相同 插值曲线过于平滑,欠拟合

对于一维均匀数据,我一般从 sigma = 0.2 开始试。你想想看,如果训练点间距是 0.1 左右,sigma 取 0.2 就能让相邻基函数有不错的重叠——这是 RBF 能「插值」而不是「过拟合」的关键。

4.3 权重求解

权重求解是 RBF 插值的核心。原理很简单:

我们有 n 个训练点 (x_i, y_i),每个训练点对应一个以 x_i 为中心的基函数。插值要求:在训练点处,基函数的加权和必须等于真实值。写成矩阵形式:

Φ · w = y

其中 Φ 是 n×n 的矩阵,Φ[i][j] = φ(||x_i - x_j||)。

代码实现:

def build_interpolation_matrix(x_train, sigma=1.0):
    """构建插值矩阵 Φ,每行对应一个训练点,每列对应一个基函数中心"""
    n = len(x_train)
    Phi = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            r = np.abs(x_train[i] - x_train[j])
            Phi[i, j] = gaussian_rbf(r, sigma)
    return Phi

# 构建矩阵并求解权重
Phi = build_interpolation_matrix(x_train, sigma=0.2)
w = np.linalg.solve(Phi, y_train)  # 直接求解线性方程组
注意:这里我用 np.linalg.solve 而不是 np.linalg.inv。直接求逆矩阵数值稳定性差,尤其当 sigma 很小时 Φ 矩阵会接近奇异。我曾经在项目里用求逆的方式算权重,结果在 sigma=0.05 时直接报错——从那以后我再也不敢偷懒了。

求解完成后,w 就是每个基函数的权重系数。你可以检查一下权重的数值范围——正常情况下应该在 -2 到 2 之间。如果出现几十上百的权重,说明 sigma 太小或者训练点太密,矩阵条件数太大了。

4.4 插值结果可视化

权重算出来了,接下来就是预测和画图。预测函数:

def rbf_interpolate(x_pred, x_train, w, sigma=1.0):
    """用训练好的 RBF 模型预测新点的值"""
    y_pred = np.zeros_like(x_pred)
    for i, x in enumerate(x_pred):
        total = 0.0
        for j, xc in enumerate(x_train):
            r = np.abs(x - xc)
            total += w[j] * gaussian_rbf(r, sigma)
        y_pred[i] = total
    return y_pred

# 在测试点上做插值
y_pred = rbf_interpolate(x_test, x_train, w, sigma=0.2)

画图我用 matplotlib:

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 5))
plt.plot(x_test, y_test, 'k--', label='真实函数', linewidth=1.5)
plt.plot(x_test, y_pred, 'b-', label='RBF插值', linewidth=2)
plt.scatter(x_train, y_train, color='red', s=60, zorder=5, label='训练点')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.legend()
plt.title('一维RBF插值结果 (sigma=0.2)')
plt.grid(alpha=0.3)
plt.show()

你会看到:红色训练点全部被蓝色插值曲线穿过,说明插值条件完全满足。在训练点之间,曲线平滑地拟合了真实函数。这就是 RBF 的魅力——精确通过每个数据点,同时保持整体平滑。

核心结论:一维 RBF 插值本质上是在「用高斯函数的加权和」去逼近任意函数。权重求解是解一个线性方程组,而 sigma 是唯一需要手动调节的超参数。

4.5 知识结构总览

下面这张图把本章的核心逻辑串起来了:

一维RBF插值完整流程 步骤1:数据生成 均匀采样 / 随机采样 步骤2:高斯基函数 φ(r) = exp(-r²/2σ²) 步骤3:权重求解 Φ · w = y 步骤4:可视化 真实 vs 插值对比 关键参数:sigma(形状参数) • sigma 过小 → 基函数只影响局部 → 插值振荡(过拟合) • sigma 适中 → 基函数合理重叠 → 插值平滑准确 • sigma 过大 → 所有基函数几乎相同 → 插值过于平滑(欠拟合)

4.6 完整代码整合

把上面所有代码拼在一起,就是一个完整的一维RBF插值工具:

import numpy as np
import matplotlib.pyplot as plt

def true_func(x):
    return np.sin(2 * np.pi * x) + 0.5 * x

def gaussian_rbf(r, sigma=1.0):
    return np.exp(-r**2 / (2 * sigma**2))

def build_interpolation_matrix(x_train, sigma=1.0):
    n = len(x_train)
    Phi = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            r = np.abs(x_train[i] - x_train[j])
            Phi[i, j] = gaussian_rbf(r, sigma)
    return Phi

def rbf_interpolate(x_pred, x_train, w, sigma=1.0):
    y_pred = np.zeros_like(x_pred)
    for i, x in enumerate(x_pred):
        total = 0.0
        for j, xc in enumerate(x_train):
            r = np.abs(x - xc)
            total += w[j] * gaussian_rbf(r, sigma)
        y_pred[i] = total
    return y_pred

# 主流程
np.random.seed(42)
x_train = np.linspace(0, 1, 10)
y_train = true_func(x_train)
x_test = np.linspace(0, 1, 200)
y_test = true_func(x_test)

sigma = 0.2
Phi = build_interpolation_matrix(x_train, sigma)
w = np.linalg.solve(Phi, y_train)
y_pred = rbf_interpolate(x_test, x_train, w, sigma)

# 可视化
plt.figure(figsize=(10, 5))
plt.plot(x_test, y_test, 'k--', label='真实函数', linewidth=1.5)
plt.plot(x_test, y_pred, 'b-', label='RBF插值', linewidth=2)
plt.scatter(x_train, y_train, color='red', s=60, zorder=5, label='训练点')
plt.xlabel('x'); plt.ylabel('f(x)')
plt.legend(); plt.grid(alpha=0.3)
plt.title(f'一维RBF插值 (sigma={sigma})')
plt.show()
我的建议:拿到这段代码后,先跑一遍看效果。然后试着改 sigma 的值——0.05、0.1、0.5、1.0,看看曲线怎么变。这种「动手调参」的体验比读十篇文章都管用。