1. 函数拟合

计算机应用常常需要使用简单函数的线性组合来拟合某个复杂函数。例如,在游戏开发中,为了实现某些渲染效果,通常会在shader中使用到正弦函数。但是,由于GPU计算正弦函数的指令数较多,性能开销比较高,开发者通常会考虑使用多项式函数𝑥𝑛的线性组合来拟合正弦函数,从而减少计算指令数,提高渲染性能。

假设我们需要计算正弦函数𝑓(𝑥)=sin(𝑥)在区间[𝜋,𝜋]上的值。为了减少性能开销, 我们考虑使用多项式函数𝑥𝑛的线性组合来逼近函数sin(𝑥),如式 1 所示。

𝑓̃(𝑥)=𝑖=0𝑛𝑐𝑖𝑥𝑖

线性组合系数𝑐𝑖的取值应该使得函数𝑓(𝑥)𝑓̃(𝑥)之间的误差最小。因此,我们先定义误差函数𝑔(𝑥),然后求得误差函数𝑔(𝑥)的最小值点,即可以得到线性组合系数𝑐𝑖

𝑔(𝑥)=𝜋𝜋(𝑓(𝑥)𝑓̃(𝑥))2

由于函数𝑔(𝑥)的最小值点必定在极小值点取得,因此求函数𝑔(𝑥)的最小值点的一种方法是,求𝑔(𝑥)的导数为0的点。另外,我们还可以使用其他方法来求解系数𝑐𝑖,该方法称为 最小二乘投影,这是后面重点介绍的方法。

对于函数sin(𝑥),我们可以使用多项式函数𝑃𝑖(𝑥)的线性组合来拟合,其中𝑃𝑖(𝑥)的定义如公式所示。

{𝑃0(𝑥)=12𝜋𝑃1(𝑥)=32𝑥𝜋32𝑃2(𝑥)=352(𝑥2𝜋23)2𝜋52𝑃3(𝑥)=572(𝑥33𝜋2𝑥5)2𝜋72

所有在区间[𝜋,𝜋]上有定义的一元连续函数能够构成函数空间𝐿(𝑅),而函数𝑃𝑖(𝑥)是该函数空间中的 基函数。这意味着函数空间𝐿(𝑅)中的任意函数,都可以使用基函数𝑃𝑖(𝑥)的线性组合来表示,即

sin(𝑥)=𝑖=0𝑐𝑖𝑃𝑖(𝑥)

虽然函数sin(𝑥)需要使用无穷项基函数𝑃𝑖(𝑥)的线性组合来表示,但是实际上,我们取基函数的前𝑛项也能够很好地来拟合函数sin(𝑥)。为了方便起见,我们取前4项基函数𝑃𝑖(𝑥)的线性组合来进行拟合,可以表示为:

sin(𝑥)𝑓̃(𝑥)=𝑖=03𝑐𝑖𝑃𝑖(𝑥)

另外,由于函数空间的基函数会满足 正交性质,则函数𝑃𝑖(𝑥)应该满足性质:

𝜋𝜋𝑃𝑖(𝑥)𝑃𝑗(𝑥)d𝑥=𝛿𝑖,𝑗

其中𝛿𝑖,𝑗的定义如下:

𝛿𝑖,𝑗={1if 𝑖𝑗0if 𝑖=𝑗

基于多项式函数𝑃𝑖(𝑥)的正交性,我们可以通过以下公式求得线性组合系数𝑐𝑖

𝑐𝑖=𝜋𝜋sin(𝑥)𝑃𝑖(𝑥)d𝑥

式 8 的证明如下:

𝑐𝑖=𝜋𝜋sin(𝑥)𝑃𝑖(𝑥)d𝑥=𝜋𝜋(𝑘=0𝑐𝑘𝑃𝑘(𝑥))𝑃𝑖(𝑥)d𝑥=𝜋𝜋𝑘=0(𝑐𝑘𝑃𝑘(𝑥)𝑃𝑖(𝑥))d𝑥=𝑐𝑖𝜋𝜋𝑃𝑖2(𝑥)d𝑥=𝑐𝑖

通过 式 8 ,我们可以得到函数𝑓̃(𝑥)中的系数如下:

𝑐0=1,𝑐1=0,𝑐2=6𝜋,𝑐3=0

带入到 式 5,可以得到函数𝑓̃(𝑥)的公式如下:

𝑓̃(𝑥)=(3152𝜋4152𝜋2)𝑥+(3525252𝜋6)𝑥30.856983𝑥0.0933877𝑥3

图 1 是函数sin(𝑥)𝑓̃(𝑥)在区间[𝜋,𝜋]的图像,其中黄色曲线为函数sin(𝑥),绿色曲线为函数𝑓̃(𝑥)。 可以看到,函数𝑓̃(𝑥)能够较好地拟合函数sin(𝑥)

图 1 正弦函数拟合 - 4项

事实上,通过增加多项式函数𝑃𝑖(𝑥)的数量,我们可以不断地提高函数𝑓̃(𝑥)对函数𝑓(𝑥)的拟合程度。当多项式的数量是6个时,即函数𝑓̃(𝑥)=𝑖=05𝑐𝑖𝑃𝑖(𝑥)时,函数图像如 图 2 所示。从图像可以看出,函数sin(𝑥)𝑓̃(𝑥)的曲线几乎一致。我们取 图 2 中的局部区间[3𝜋10,7𝜋10],如 图 3 所示,可以看到函数sin(𝑥)𝑓̃(𝑥)的曲线误差也很小。

图 2 正弦函数拟合 - 6项

当函数𝑓̃(𝑥)中多项式函数𝑃𝑖(𝑥)的项数趋于无穷时,其极限为sin(𝑥),能够完美地拟合函数sin(𝑥)

图 3 正弦函数拟合(局部) - 6项