径向(Radial Direction)是指沿半径的直线方向,或垂直于轴的直线方向1。径向基函数(Radial Basis Function,RBF)是一个取值仅依赖于到原点距离的实值函数2。在机器学习中,RBF 常被用作支持向量机的核函数。而我们在这里主要讨论 RBF 应用于插值的情况。

什么是插值

插值(Interpolation)是一种函数拟合的方式3。给定一组在采样点 $\{x_i\}_{i=1}^n$ 上的测量值,点 $x_i$ 上的测量值为 $f_i$ ,我们希望找到一个「插值函数」 $s(x)$ 使得我们能够获知其他采样点的值。

这里的插值函数 $s(x)$ 需要满足插值条件 $s(x_{i}) = f_{i}$ ,也就是说,这个插值函数必须精确匹配到给定的观测值。这里需要提一下「插值」和「逼近」这两种拟合方式的区别。函数拟合有两种,一种是插值,另一种是逼近。在实际应用中,我们比较多使用的方法是逼近,甚至很多时候会混用「拟合」和「逼近」这两个词,因为很多观测点的数据测量本来就存在误差,使用插值的方式会保留这些误差,而且约束过强。但使用逼近求得的函数并不一定确保观测点的值相等,而插值则能确保这一点。

为了方便求解,我们一般会假设插值函数 $s(x)$ 是一组线性基函数 $\psi_{i}(x)$ 的叠加:

$$ s(x) = \sum_{i=1}^{n}\lambda_{i}\psi_{i} $$

此时,这个表示方法的便利之处在于我们可以用解线性系统的方式来对其进行求解,使用矩阵的形式可以表示为:

$$ \mathrm{A}\mathrm{\lambda} = \mathrm{f} $$

其中 $\mathrm{f} = [f_{1}, \ldots, f_{n}]^{T}$ 是一个向量,描述了在该组采样点上的所有对应的测量值,而 $\mathrm{\lambda} = [\lambda_{1}, \ldots, \lambda_{n}]^{T}$ 则是系数向量。矩阵 $\mathrm{A}$ 是一个 $n \times n$ 的矩阵,被称为插值矩阵,其中的值由基函数 $\psi$ 计算得出:

$$ a_{ij} = \psi_{j}(x_{i}) $$

RBF 公式理解

使用 RBF 作为插值函数的公式如下4

$$ s(x) = \sum_{i=1}^{n} \lambda_{i} \phi(\lVert x - x_{i} \rVert) $$

前面也提到了,等式左边的 $s(x)$ 是表达了 $x$ 点上的某种测量值,这个测量值可以是任意东西,例如颜色、缩放、温度等等。在 RBF 插值中,采样点就是空间中的位置点。简单来说,RBF 的插值为我们提供了这样一种方法:已知空间中若干个位置上某个属性的值,此时可以求解出空间中任意一个位置的对应属性值。等式右边的每个 $x_{i}$ 就是一个观测点,也就是这里有 $n$ 个观测点。而 $\lVert x - x_{i} \rVert$ 就是当前点 $x$ 和 $x_{i}$ 的距离。这里的 $\lambda_{i}$ 对应了每个观测点对目标点的影响系数,实际需要求解的就是每个 $\lambda_{i}$ 的值。

上式中的 $\phi$ 就是 RBF,它以 $x$ 和 $x_{i}$ 之间的距离作为参数,在此基础上进行变换。$\phi$ 有很多中选择,在 scipy 的文档里可以看到有如下的常用的函数5

'multiquadric': sqrt((r/self.epsilon)**2 + 1)
'inverse': 1.0/sqrt((r/self.epsilon)**2 + 1)
'gaussian': exp(-(r/self.epsilon)**2)
'linear': r
'cubic': r**3
'quintic': r**5
'thin_plate': r**2 * log(r)

简单情况就直接用 linear 版本即可,此时 $\phi(x) = x$ 就是 $\text{identity}$ 函数。这里的 $\lVert x - x_{i} \rVert$ 也有讲究,有不同的距离类型,简单情况就用欧式距离即可。根据实际需要,可以尝试替换不同的 RBF 和距离函数,可以插值出不同结果。

应用:颜色插值

假设空间中存在 $n$ 个已知点的颜色,用 $x_i$ 表示第 $i$ 个已知点,我们希望在给出空间中任意一点 $y$ 的位置时,计算该点的颜色,我们就可以使用 RBF 插值来实现。显然,这里应该将这 $n$ 个已知点的位置互相进行计算,形成 $n$ 个方程,未知数就是前面提到的 $\lambda$ :

$$ \begin{cases} s_{1} & = \lambda_{1} \phi(\lVert x_{1} - x_{1} \rVert) + \lambda_{2} \phi(\lVert x_{1} - x_{2} \rVert) + \ldots + \lambda_{n} \phi(\lVert x_{1} - x_{n} \rVert) \\ s_{2} & = \lambda_{1} \phi(\lVert x_{2} - x_{1} \rVert) + \lambda_{2} \phi(\lVert x_{2} - x_{2} \rVert) + \ldots + \lambda_{n} \phi(\lVert x_{2} - x_{n} \rVert) \\ & \vdots \\ s_{n} & = \lambda_{1} \phi(\lVert x_{n} - x_{1} \rVert) + \lambda_{2} \phi(\lVert x_{n} - x_{2} \rVert) + \ldots + \lambda_{n} \phi(\lVert x_{n} - x_{n} \rVert) \end{cases} $$

对于这个应用场景而言,我们将 $s_i$ 设为第 $i$ 个观测点的红色通道的颜色值。也就是我们认为空间中每个点的红色通道颜色值和对应点与所有观测点之间距离存在某种关系。当求解出每一个 $\lambda_{i}$ 之后,我们就获得了插值函数 $s(x)$ 。

这里可以写成矩阵 $\mathrm{A}\mathrm{x} = \mathrm{b}$ 的形式方便使用矩阵数学库进行计算:

$$ \begin{bmatrix} \phi(\lVert x_{1} - x_{1} \rVert) & \phi(\lVert x_{1} - x_{2} \rVert) & \cdots & \phi(\lVert x_{1} - x_{n} \rVert) \\ \phi(\lVert x_{2} - x_{1} \rVert) & \phi(\lVert x_{2} - x_{2} \rVert) & \cdots & \phi(\lVert x_{2} - x_{n} \rVert) \\ \vdots & \vdots & \ddots & \vdots \\ \phi(\lVert x_{n} - x_{1} \rVert) & \phi(\lVert x_{n} - x_{2} \rVert) & \cdots & \phi(\lVert x_{n} - x_{n} \rVert) \end{bmatrix} \begin{bmatrix} \lambda_{1} \\ \lambda_{2} \\ \vdots \\ \lambda_{n} \end{bmatrix} = \begin{bmatrix} s_{1} \\ s_{2} \\ \vdots \\ s_{n} \end{bmatrix} $$

设上述等式左侧两个矩阵分别为 $\Phi$ 和 $\Lambda_r$ 右侧的矩阵为 $\mathrm{S}_r$ ,则我们有:

$$ \Phi \Lambda_r = \mathrm{S}_r $$

因此:

$$ \Lambda_r = \Phi^{-1} \mathrm{S}_r $$

求解这个等式后我们可以得到所有的 $\lambda_i$ 的值,此时就可以计算任意的 $x$ 点上的 $s(x)$ 了:

$$ s(x) = \lambda_1 \phi(\lVert x - x_{1} \rVert) + \lambda_2 \phi(\lVert x - x_{2} \rVert) + \cdots + \lambda_n \phi(\lVert x - x_{n} \rVert) $$

显然此时将观测点中的任意一点 $x_i$ 的位置代入 $s(x)$ ,可以求解出和我们一开始直接算出的 $s_i$ 一模一样的结果,这是正是前面提到的插值函数的特点,即确保每个数据点的值一样。那么,此时代入任意一个新的点 $y$ 的位置,就可以计算出 $y$ 点的颜色值了。

在知道红色通道颜色值的插值计算方式后,蓝绿通道的计算方法就显而易见了,我们可以将其合并成一个矩阵计算:

$$ \begin{bmatrix} \phi(\lVert x_{1} - x_{1} \rVert) & \phi(\lVert x_{1} - x_{2} \rVert) & \cdots & \phi(\lVert x_{1} - x_{n} \rVert) \\ \phi(\lVert x_{2} - x_{1} \rVert) & \phi(\lVert x_{2} - x_{2} \rVert) & \cdots & \phi(\lVert x_{2} - x_{n} \rVert) \\ \vdots & \vdots & \ddots & \vdots \\ \phi(\lVert x_{n} - x_{1} \rVert) & \phi(\lVert x_{n} - x_{2} \rVert) & \cdots & \phi(\lVert x_{n} - x_{n} \rVert) \end{bmatrix} \begin{bmatrix} \lambda_{1r} & \lambda_{1g} & \lambda_{1b} \\ \lambda_{2r} & \lambda_{2g} & \lambda_{2b} \\ \vdots & \vdots & \vdots \\ \lambda_{nr} & \lambda_{ng} & \lambda_{nb} \end{bmatrix} = \begin{bmatrix} s_{1r} & s_{1g} & s_{1b} \\ s_{2r} & s_{2g} & s_{2b} \\ \vdots & \vdots & \vdots \\ s_{nr} & s_{ng} & s_{nb} \end{bmatrix} $$

类似地,我们将上式写为:

$$ \Phi \Lambda = \mathrm{S} $$

因此:

$$ \Lambda = \Phi^{-1} \mathrm{S} $$

当我们有 $m$ 个点需要求解颜色时,我们就可以将所有点的数据合并为一个矩阵进行计算:

$$ \Phi^{\prime} = \begin{bmatrix} \phi(\lVert y_{1} - x_{1} \rVert) & \phi(\lVert y_{1} - x_{2} \rVert) & \cdots & \phi(\lVert y_{1} - x_{n} \rVert) \\ \phi(\lVert y_{2} - x_{1} \rVert) & \phi(\lVert y_{2} - x_{2} \rVert) & \cdots & \phi(\lVert y_{2} - x_{n} \rVert) \\ \vdots & \vdots & \ddots & \vdots \\ \phi(\lVert y_{m} - x_{1} \rVert) & \phi(\lVert y_{m} - x_{2} \rVert) & \cdots & \phi(\lVert y_{m} - x_{n} \rVert) \end{bmatrix} $$

那么:

$$ \begin{bmatrix} s_{1r} & s_{1g} & s_{1b} \\ s_{2r} & s_{2g} & s_{2b} \\ \vdots & \vdots & \vdots \\ s_{mr} & s_{mg} & s_{mb} \end{bmatrix} = \Phi^{\prime} \Lambda $$

Demo 工程

如果上面的公式看着头疼,这里也用 Unity 实现了一个小 Demo,可以对照代码查看。运行起来后,场景中的 3 个方块相当于上面提到的采样点 $x$ ,而场景中的 5 个球就是待求解的 $y$ ,拖动这些球就可以看到它们在不同位置的插值结果了:

RBF Demo

总结

RBF 是一个常用的插值方法,除了这种简单的颜色插值之外,还能被用于网格变形等场合(其实就是将 $s_i$ 设为位置偏移),理解了上面这些原理后,其他的场景也就都能触类旁通了。

参考资料