Cardinal B-Splines 曲线拟合

前言

前面介绍了spline的基函数,没想到以前觉得很简单的东西,能够玩出这么多花样。我的初衷本不过是想了解一下spline回归的基本思想,没想到陷进去了,索性弄得透彻点些吧

目标

这篇日志主要是解释一下Cardinal B-Splines的求导,后面给出一个spline的平滑计算实例。平滑不等于插值,所以spline平滑和spline的内插是不同的,前者不通过控制点,后者通过。我以前所理解的spline其实是插值,而不是平滑

Cardinal B-Splines

下面要介绍的是spline的一种特殊形式,也是一种简单的形式,就是线段按照均匀分割,称为Cardinal B-Splines,其有一些特殊的性质,导致其应用比较广泛。

首先,我们先了解一下离散卷积的概念
y(n)=i=h(ni)x(i)=h(n)x(n) y(n) = \sum_{i=-\infty }^{\infty } h(n-i)x(i) = h(n)*x(n)
y(n),x(n),h(x)y(n),x(n),h(x)分别对应一个LTI系数的输出,输入和响应。LTI系统对与一个单位冲击是有一个连续的响应过程,所以,在连续输入时,y的输出可以看作是各个不同时延的幅度不同的冲击不同的脉冲共同作用下的输出。那么,这和Cardinal B-Splines有什么联系呢?
先回顾一下,spline的基函数递推生成公式,
Ni,0(u)={1,uiu<ui+10,otherwiseNi,p(u)=uuiui+puiNi,p1(u)+ui+p+1uui+p+1ui+1Ni+1,p1(u) N_{i,0}(u) = \left\{\begin{matrix} 1,& u_i \leq u < u_{i+1} \\ 0, & otherwise \end{matrix}\right.\\ N_{i,p}(u) = \tfrac{u-u_i}{u_{i+p}-u_i} N_{i,p-1}(u) + \tfrac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)
很多文献喜欢将Ni,pN_{i,p}记为Bi,pB_{i,p},在此文中,两者含义相同。假设分割区间长度为h,那么上式可以转换为
Ni,p(u)=uuiphNi,p1(u)+ui+p+1uphNi+1,p1(u) N_{i,p}(u) = \tfrac{u-u_i}{p*h} N_{i,p-1}(u) + \tfrac{u_{i+p+1}-u}{p*h}N_{i+1,p-1}(u)
我们令h(u)=uh(u) = u,我们可以看到N_{i,p}(u)是由两组卷积构成的,这解释了前面门函数往三角波,再往更复杂的基函数变换的过程。另外一个性质上,Ni,p1(u)N_{i,p-1}(u)Ni+1,p1(u)N_{i+1,p-1}(u),只差了一个时延h,看前面那篇文章的图就很直观了。
Ni,pN_{i,p}导数如下,证明涉过程及到高阶差分方程,有点复杂,有兴趣可以看参考文献1。从结果看,spline应该能看成一种差分方程
dNi,p(u)du=pti+ptiNi,p1(u)pti+p+1ti+1Ni+1,p1(u)=1h(Ni,p1(u)Ni+1,p1(u))  . \frac{dN_{i,p}(u)}{du} = \frac{p}{t_{i+p}-t_i}N_{i,p-1}(u)- \frac{p}{t_{i+p+1}-t_{i+1}}N_{i+1,p-1}(u)=\frac{ 1}{h}(N_{i,p-1}(u)- N_{i+1,p-1}(u))\;.
Ni,pN_{i,p}的导数和自身有点像,这个结果以后会用到,先放放。

Cardinal B-Spline 曲线拟合

搞了这么久,本来的目的是想做一个曲线Cardinal B-Spline,先用一下最简单的曲线拟合。

基本原理

f(x)f(x)是已知函数,Ni,p(x)N_{i,p}(x)是生成的p次基函数
f(x)=i=0n1ciNi,p(x) f(x) = \sum_{i=0}^{n-1} c_i N_{i,p}(x)
cic_i为待求系数,我们这里先用最小二乘法估计
y,xRm×1,yi=f(xi),N=[N0,p,,Nn1,p],NRm×n,\bf y,x \in \mathbb{R}^{m\times 1},y_i = f(x_i),N =[N_{0,p},\dots,N_{n-1,p}],N \in \mathbb{R}^{m\times n},
上式可以写为
y=Ncc^=(NTN)1NTy \bf y = Nc \Rightarrow \hat{c} = (N^TN)^{-1}N^Ty
由了c\bf c和基函数,那么,在给定范围内的任意x都可以给出它的spline曲线上的值。
好了,现在终于可以试一下手了

代码

代码依旧很烂,懒得整,主要功能是生成正弦信号y=sin(x),x(02π)y =sin(x), x\in (0,2\pi),加入少量噪声。分段节点knots 按两种模式来设置,一种是均匀分割,第二种是按照p次重插入起始点,即(0,0,,2π,2π)(0,0,\dots,2\pi,2\pi),然后测试0-3次的B-spline,详情见代码

clear;
%Generating simulation data
n = 101;
max_x = 2*3.14159;
x = linspace(0,max_x,n);
knots = x(1:5:101);
y = sin(x)+(rand(1,n)-0.5)/3;
%i (1-4) test 1-4 order spline fit
%i (5-8) test 1-4 order spline fit with  multiplicity
figure('color','w');
fmt = "%d order spline"; 
for i = 1:1:8
    j = i;
    if j>=5
        fmt = "%d order spline with  multiplicity ";
         knots = [ones(1,i)*0,ones(1,i)*max_x];
        if j==5
            figure('color','w');
        end
        j = j-4;
    end
    N  = GenerateBasicFunctionCurves((j-1),x,knots);
    N = N';
    c = pinv(N)*y';
    yfit = N*c;
    subplot(2,2,j)
    plot(x,y,'ob');
    hold on;
    plot(x,yfit,'-r','Linewidth',1.5);
    str = sprintf(fmt,j);
    title(str);
    plotsetting
end
function v  = SplineBasisFunction(i,p,u,knots)
u_i  = knots(i);
u_i1 = knots(i+1);
if p==0
    if u>=u_i && u<u_i1
        v = 1;return;
    end
    v = 0;return;
end
u_ip1 = knots(i+p+1);u_ip = knots(i+p);
w1 = 0;w2 = 0;
if u_ip~=u_i
    w1=(u-u_i)/(u_ip-u_i);
end
if u_ip1~=u_i1
    w2=(u_ip1-u)/(u_ip1-u_i1);
end
v = w1*SplineBasisFunction(i,p-1,u,knots)+w2*SplineBasisFunction(i+1,p-1,u,knots);
end
function bf = GenerateBasicFunctionCurves(p,x,knots)
for i = 1:1:length(knots)-1-p
    for j = 1:1:length(x)
        bf(i,j) = SplineBasisFunction(i,p,x(j),knots);
    end
end
end
function plotsetting()
a=gca;
a.FontSize=20;
set(gca,'fontweight','bold');
set(gca,'Linewidth',2);
end

结果

均匀分割的问题在于,基函数总是会试图去拟合在knot上的点,导致spline怎么看都不太光滑。
在这里插入图片描述
多重插入节点
在这里插入图片描述
多重节点在阶数少的时候,基函数表达能力比较弱,所以拟合效果也出所料地差,但是到了三阶的时候明显效果飞升,平滑效果要好于前者。

参考文献

  1. Carl de Boor (1978). A Practical Guide to Splines.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章