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 均匀采样。为什么不用随机点?嗯,一维演示时均匀点能更清楚地展示插值行为。实际项目中,如果你知道数据分布不均匀,那就要考虑用聚类或自适应采样了——这个我们后面章节会讲。
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 的魅力——精确通过每个数据点,同时保持整体平滑。
4.5 知识结构总览
下面这张图把本章的核心逻辑串起来了:
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()