分段三次样条
一、插值和样条?
给定若干已知点位,连接、逼近这些点位的方式有很多种,我们既可以给定一个足够阶数的多项式一次性穿过、逼近这些点位,这些方法可以是:牛顿插值法、拉格朗日插值法;也可以按段给出每个区间的表达式,比如说三次样条。
上面展示的就是按段给出的表达式,按段一条条给出的,所以叫做样条。当然你也可以用一个多项式穿过这些点:
红色的线就是用一个多项式连接的方法。用一个多项式拟合所有的点位不都是好的,尤其是均匀分布的插值点两端因为它会产生龙格现象:
从上图可以看出,左右两端的拟合对于原函数完全不匹配。缓解方法有很多种,可以使用切比雪夫或者分段三次样条进行处理。也就是文章的重点,分段三次多项式样条。
二、分段三次样条(自然边界条件)
样条的表达式是通过多个分段函数给出的,约束条件有:
- 满足节点自变量递增关系;
- 相邻多项式之间一二阶导数连续;
- 多项式的次数是3次;
如果每相邻两个点之间的连线是三次多项式,那么对应的整段组合而成的曲线就叫做三次样条函数。假设点数是 n + 1 n+1 n+1,那么段数为: n n n
- 三次多项式有四个未知数,一共有 n n n 个,所以待求的方程组个数为 4 n 4n 4n;
- 一共有 n + 1 n+1 n+1 个点,我已有 ( n + 1 ) (n+1) (n+1) 个方程组,这样一来我就剩下 4 n − ( n + 1 ) = 3 n − 1 4n-(n+1)=3n-1 4n−(n+1)=3n−1;
- 一共有 n − 1 n-1 n−1 个衔接点,衔接点处函数值连续、一阶导连续,二阶导连续,提供 3 ( n − 1 ) 3(n-1) 3(n−1)个方程,剩余 3 n − 1 − 3 ( n − 1 ) = 2 3n-1-3(n-1)=2 3n−1−3(n−1)=2
根据不同的约束可以将三次样条分为三类:
- 端点处二阶导函数值约束,如果都是0,则为自然样条;
- 端点处一阶导数约束;
- 周期约束,略;
给定以下插值点:
x x x | t 0 t_0 t0 | t 1 t_1 t1 | ⋯ \cdots ⋯ | t n t_n tn |
---|---|---|---|---|
y y y | y 0 y_0 y0 | y 1 y_1 y1 | ⋯ \cdots ⋯ | y n y_n yn |
根据这个表格结合三次样条多项式的定义:
S
(
x
)
=
{
S
0
(
x
)
x
∈
[
t
0
,
t
1
]
S
1
(
x
)
x
∈
[
t
1
,
t
2
]
⋮
S
n
−
1
(
x
)
x
∈
[
t
n
−
1
,
t
n
]
S(x)=\left\{ \begin{aligned} &S_0(x)\quad x\in[t_0,t_1]\\ &S_1(x)\quad x\in[t_1,t_2]\\ &\vdots\\ &S_{n-1}(x)\quad x\in[t_{n-1},t_n]\\ \end{aligned} \right.
S(x)=⎩
⎨
⎧S0(x)x∈[t0,t1]S1(x)x∈[t1,t2]⋮Sn−1(x)x∈[tn−1,tn]
在此之前我们先来推导区间
[
t
i
,
t
i
+
1
]
[t_i,t_{i+1}]
[ti,ti+1] 上
S
i
(
x
)
S_i(x)
Si(x) 的表达式。首先我们定义一组数
z
i
=
S
′
′
(
t
i
)
z_i=S''(t_i)
zi=S′′(ti),因为
S
′
′
S''
S′′ 在每个内结点上连续,所以显然,对于
0
≤
i
≤
n
0\le i\le n
0≤i≤n 存在
z
i
z_i
zi 满足:
lim
x
→
t
i
+
S
′
′
(
x
)
=
lim
x
→
t
i
−
S
′
′
(
x
)
\lim_{x\rightarrow t_i^+}S''(x)=\lim_{x\rightarrow t_i^-}S''(x)
x→ti+limS′′(x)=x→ti−limS′′(x)
因为
S
i
S_i
Si 是在
[
t
i
,
t
i
+
1
]
[t_i,t_{i+1}]
[ti,ti+1] 上的三次多项式,因此是满足
S
i
′
′
(
t
i
)
=
z
i
S''_i(t_i)=z_i
Si′′(ti)=zi、
S
i
′
′
(
t
i
+
1
)
=
z
i
+
1
S''_i(t_{i+1})=z_{i+1}
Si′′(ti+1)=zi+1 的线性函数,根据根据拉格朗日插值法有:
S
i
′
′
(
x
)
=
z
i
h
i
(
t
i
+
1
−
x
)
+
z
i
+
1
h
i
(
x
−
t
i
)
S''_i(x)=\frac{z_i}{h_i}(t_{i+1}-x)+\frac{z_{i+1}}{h_i}(x-t_i)
Si′′(x)=hizi(ti+1−x)+hizi+1(x−ti)
其中
h
i
=
t
i
+
1
−
t
i
h_i=t_{i+1}-t_i
hi=ti+1−ti 。为了利用插值点,我们对
(
3
)
(3)
(3) 对变量
x
x
x积分两次有:
S
i
(
x
)
=
z
i
6
h
i
(
t
i
+
1
−
x
)
3
+
z
i
+
1
6
h
i
(
x
−
t
i
)
3
+
C
(
x
−
t
i
)
+
D
(
t
i
+
1
−
x
)
S_i(x)=\frac{z_i}{6h_i}(t_{i+1}-x)^3+\frac{z_{i+1}}{6h_i}(x-t_i)^3+C(x-t_i)+D(t_{i+1}-x)
Si(x)=6hizi(ti+1−x)3+6hizi+1(x−ti)3+C(x−ti)+D(ti+1−x)
这里直接用整体法进行积分不要直接拆开会简单一些
其中 C C C D D D 是积分常数。
代入插值条件 S i ( t i ) = y i S_i(t_i)=y_i Si(ti)=yi 和 S i ( t i + 1 ) = y i + 1 S_i(t_{i+1})=y_{i+1} Si(ti+1)=yi+1 作用在 S i S_i Si 就可以去确定 C C C D D D 两个常数,其结果为:
S i ( x ) = z i 6 h i ( t i + 1 − x ) 3 + z i + 1 6 h i ( x − t i ) 3 + ( y i + 1 h i − z i + 1 h i 6 ) ( x − t i ) + ( y i h i − z i h i 6 ) ( t i + 1 − x ) \begin{aligned} S_i(x) = \frac{z_i}{6h_i}(t_{i+1} - x)^3 + \frac{z_{i+1}}{6h_i}(x - t_i)^3 + \left(\frac{y_{i+1}}{h_i} - \frac{z_{i+1} h_i}{6}\right)(x - t_i) + \left(\frac{y_i}{h_i} - \frac{z_i h_i}{6}\right)(t_{i+1} - x) \end{aligned} Si(x)=6hizi(ti+1−x)3+6hizi+1(x−ti)3+(hiyi+1−6zi+1hi)(x−ti)+(hiyi−6zihi)(ti+1−x)
此时整个方程只有二阶导数是未知的,为了利用一阶导数的连续性,我们对
(
5
)
(5)
(5) 微分一次代入
S
i
−
1
′
(
t
i
)
=
S
i
′
(
t
i
)
S'_{i-1}(t_i)=S_i'(t_i)
Si−1′(ti)=Si′(ti) 条件:
S
i
′
(
t
i
)
=
−
h
i
3
z
i
−
h
i
6
z
i
+
1
−
y
i
h
i
+
y
i
+
1
h
i
S'_i(t_i)=-\frac{h_i}{3}z_i-\frac{h_i}{6}z_{i+1}-\frac{y_i}{h_i}+\frac{y_{i+1}}{h_i}
Si′(ti)=−3hizi−6hizi+1−hiyi+hiyi+1
同理,可以求得
S
i
−
1
′
(
t
i
)
S_{i-1}'(t_i)
Si−1′(ti):
S
i
−
1
′
(
t
i
)
=
h
i
−
1
6
z
i
−
1
+
h
i
−
1
3
z
i
−
y
i
h
i
−
1
+
y
i
h
i
−
1
S'_{i-1}(t_i)=\frac{h_{i-1}}{6}z_{i-1}+\frac{h_{i-1}}{3}z_i-\frac{y_i}{h_{i-1}}+\frac{y_i}{h_{i-1}}
Si−1′(ti)=6hi−1zi−1+3hi−1zi−hi−1yi+hi−1yi
因为一阶导数连续,所以:
h
i
−
1
z
i
−
1
+
2
(
h
i
+
h
i
−
1
)
z
i
+
h
i
z
i
+
1
=
6
h
i
(
y
i
+
1
−
y
i
)
−
6
h
i
−
1
(
y
i
−
y
i
−
1
)
h_{i-1}z_{i-1}+2(h_i+h_{i-1})z_i+h_iz_{i+1}=\frac{6}{h_i}(y_{i+1}-y_i)-\frac{6}{h_{i-1}}(y_i-y_{i-1})
hi−1zi−1+2(hi+hi−1)zi+hizi+1=hi6(yi+1−yi)−hi−16(yi−yi−1)
上面的方程对于
i
=
1
,
2
,
⋯
,
n
−
1
i=1,2,\cdots,n-1
i=1,2,⋯,n−1 成立。注意这个式子没有对两个端点成立,这是以为他们前面和后面都没有对应的三次多项式,所以我们给出了一个含有
n
+
1
n+1
n+1 个未知量
z
0
,
z
1
,
⋯
,
z
n
z_0,z_1,\cdots,z_n
z0,z1,⋯,zn 的一个
n
−
1
n-1
n−1 个方程的线性方程组。只要任选
z
0
z_0
z0
z
1
z_1
z1 就可以计算出确定这个分段多项式方程。最常见的方法就是取
z
0
=
z
n
=
0
z_0=z_n=0
z0=zn=0,由此得到的样条函数称为自然三次样条。对于
1
≤
i
≤
n
−
1
1\le i\le n-1
1≤i≤n−1 和
z
0
=
z
n
=
0
z_0=z_n=0
z0=zn=0 :
h
0
z
0
+
2
(
h
1
+
h
0
)
z
1
+
h
1
z
2
=
6
h
1
(
y
2
−
y
1
)
−
6
h
0
(
y
1
−
y
0
)
h
1
z
1
+
2
(
h
2
+
h
1
)
z
2
+
h
2
z
3
=
6
h
2
(
y
3
−
y
2
)
−
6
h
1
(
y
2
−
y
1
)
h
2
z
2
+
2
(
h
3
+
h
2
)
z
3
+
h
3
z
4
=
6
h
3
(
y
4
−
y
3
)
−
6
h
2
(
y
3
−
y
2
)
⋯
h
n
−
2
z
n
−
2
+
2
(
h
n
−
1
+
h
n
−
2
)
z
n
−
1
+
h
n
−
1
z
n
=
6
h
n
−
1
(
y
n
−
y
n
−
1
)
−
6
h
n
−
2
(
y
n
−
1
−
y
n
−
2
)
h_0z_0+2(h_1+h_0)z_1+h_1z_2=\frac{6}{h_1}(y_2-y_1)-\frac{6}{h_0}(y_1-y_0)\\ h_1z_1+2(h_2+h_1)z_2+h_2z_3=\frac{6}{h_2}(y_3-y_2)-\frac{6}{h_1}(y_2-y_1)\\ h_2z_2+2(h_3+h_2)z_3+h_3z_4=\frac{6}{h_3}(y_4-y_3)-\frac{6}{h_2}(y_3-y_2)\\ \cdots\\ h_{n-2}z_{n-2}+2(h_{n-1}+h_{n-2})z_{n-1}+h_{n-1}z_n=\frac{6}{h_{n-1}}(y_{n}-y_{n-1})-\frac{6}{h_{n-2}}(y_{n-1}-y_{n-2})\\
h0z0+2(h1+h0)z1+h1z2=h16(y2−y1)−h06(y1−y0)h1z1+2(h2+h1)z2+h2z3=h26(y3−y2)−h16(y2−y1)h2z2+2(h3+h2)z3+h3z4=h36(y4−y3)−h26(y3−y2)⋯hn−2zn−2+2(hn−1+hn−2)zn−1+hn−1zn=hn−16(yn−yn−1)−hn−26(yn−1−yn−2)
令
u
i
=
2
(
h
i
+
h
i
−
1
)
u_i=2(h_i+h_{i-1})
ui=2(hi+hi−1)
b
i
=
6
h
i
(
y
i
+
1
−
y
i
)
b_i=\frac{6}{h_i}(y_{i+1}-y_i)
bi=hi6(yi+1−yi)
v
i
=
b
i
−
b
i
−
1
v_i=b_i-b_{i-1}
vi=bi−bi−1
h
0
z
0
+
u
1
z
1
+
h
1
z
2
=
v
1
h
1
z
1
+
u
2
z
2
+
h
2
z
3
=
v
2
h
2
z
2
+
u
3
z
3
+
h
3
z
4
=
v
3
⋯
h
n
−
2
z
n
−
2
+
u
n
−
1
z
n
−
1
+
h
n
−
1
z
n
=
v
n
−
1
h_0z_0+u_1z_1+h_1z_2=v_1\\ h_1z_1+u_2z_2+h_2z_3=v_2\\ h_2z_2+u_3z_3+h_3z_4=v_3\\ \cdots\\ h_{n-2}z_{n-2}+u_{n-1}z_{n-1}+h_{n-1}z_n=v_{n-1}\\
h0z0+u1z1+h1z2=v1h1z1+u2z2+h2z3=v2h2z2+u3z3+h3z4=v3⋯hn−2zn−2+un−1zn−1+hn−1zn=vn−1
写成矩阵形式
[
u
1
h
1
h
1
u
2
h
2
h
2
u
3
h
3
⋮
h
n
−
3
u
n
−
2
h
n
−
2
h
n
−
2
u
n
−
1
]
[
z
1
z
2
z
3
⋮
z
n
−
2
z
n
−
1
]
=
[
v
1
v
2
v
3
⋮
v
n
−
2
v
n
−
1
]
\begin{bmatrix} u_1&h_1& & & &\\ h_1&u_2&h_2& & &\\ &h_2&u_3&h_3 && &\\ \vdots\\ & & & h_{n-3}&u_{n-2}&h_{n-2}\\ & & & &h_{n-2}&u_{n-1} \end{bmatrix}\begin{bmatrix}z_1\\z_2\\z_3\\\vdots\\z_{n-2}\\z_{n-1}\end{bmatrix} =\begin{bmatrix}v_1\\v_2\\v_3\\\vdots\\v_{n-2}\\v_{n-1}\end{bmatrix}
u1h1⋮h1u2h2h2u3h3hn−3un−2hn−2hn−2un−1
z1z2z3⋮zn−2zn−1
=
v1v2v3⋮vn−2vn−1
其中
h
i
=
t
i
+
1
−
t
i
u
i
=
2
(
h
i
+
h
i
−
1
)
b
i
=
6
h
i
(
y
i
+
1
−
y
i
)
v
i
=
b
i
−
b
i
−
1
\begin{aligned} h_i&=t_{i+1}-t_i\\ u_i&=2(h_i+h_{i-1})\\ b_i&=\frac{6}{h_i}(y_{i+1}-y_i)\\ v_i&=b_i-b_{i-1} \end{aligned}
hiuibivi=ti+1−ti=2(hi+hi−1)=hi6(yi+1−yi)=bi−bi−1
(
11
)
(11)
(11) 的系数矩阵是一个三对角矩阵,求解出对应的二阶导数值
z
i
z_i
zi 结合
(
5
)
(5)
(5) 便可得到任意区间对应的插补函数值。
三、实现
为了求得每段的表达式,我们需要求解 ( 11 ) (11) (11) 这个线性方程组,简单用python实现了以下并将其与标准库对比:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline
from scipy.linalg import solve_banded
# 给定数据点
P = np.array(
[[2, 3], [3, 5], [7, 11], [10, 20], [14, 10], [28, 34], [33, 23]],
)
x = P[:, 0]
y = P[:, 1]
def getUHV(x,y):
h=np.zeros(len(x)-1)
u=np.zeros(len(h)-1)
b=np.zeros(len(y)-1)
v=np.zeros(len(b)-1)
h=np.diff(x)
for i in range(len(u)):
u[i]=2*(h[i+1]+h[i])
for i in range(len(b)):
b[i]=6*(y[i+1]-y[i])/h[i]
v=np.diff(b)
return u,h,v
def getZ(u,h,v):
# | u_1 h_1 0 0 ... 0 0 | | z_1 | = | v_1 |
# | h_1 u_2 h_2 0 ... 0 0 | | z_2 | = | v_2 |
# | 0 h_2 u_3 h_3 ... 0 0 | | z_3 | = | v_3 |
# | . . . . ... . . | | . | = | . |
# | . . . . ... . . | | . | = | . |
# | . . . . ... . . | | . | = | . |
# | 0 0 0 ... h_{n-3} u_{n-2} h_{n-2} | | z_{n-2} | = | v_{n-2} |
# | 0 0 0 ... 0 h_{n-2} u_{n-1} | | z_{n-1} | = | v_{n-1} |
M=np.zeros((len(v),len(v)))
np.fill_diagonal(M, u)
np.fill_diagonal(M[1:],h[1:])
np.fill_diagonal(M[:,1:], h[1:])
z=np.linalg.solve(M,v)
z=np.insert(z,0,0)
z=np.append(z,0)
return z
def cubicInterpolation(xx, z):
n = len(x) - 1
value = np.zeros_like(xx)
count = 0
for xi in xx:
for k in range(n):
if x[k] <= xi <= x[k + 1]: # chech which interval x falls into
s1 = ((x[k + 1] - xi) ** 3) * (z[k] / (6 * h[k]))
s2 = ((xi - x[k]) ** 3) * (z[k + 1] / (6 * h[k]))
s3 = (y[k + 1] / h[k] - z[k + 1] * h[k] / 6) * (xi - x[k])
s4 = (y[k] / h[k] - z[k] * h[k] / 6) * (x[k + 1] - xi)
value[count] = s1 + s2 + s3 + s4
count += 1
break
return value
# 自定义的三次样条插值
u, h, v = getUHV(x, y)
z = getZ(u, h, v)
xx = np.linspace(min(x), max(x), 50000)
yy_custom = cubicInterpolation(xx, z)
# 使用标准库进行三次样条插值
cs = CubicSpline(x, y, bc_type="natural")
yy_scipy = cs(xx)
# 绘图
plt.figure(figsize=(10, 6))
plt.plot(x, y, "o", label="Data points")
plt.plot(xx, yy_custom, "-", label="Custom cubic spline interpolation")
plt.plot(xx, yy_scipy, "--", label="Scipy cubic spline interpolation")
plt.legend()
plt.xlabel("x")
plt.ylabel("y")
plt.title("Comparison of Custom and Scipy Cubic Spline Interpolation")
plt.show()
在实际工程实现中,通常直接通过求解线性方程组的方式进行求解,利用三对角方程的性质递归求解系数比较合适。当然对于空间中的点需要进行一些参数化处理,对C++ 代码实现感兴趣,可以参考我上传的在github的代码:https://github.com/FISHFISHFISHLOVECAT/cublic_spline_curve
四、优缺点分析
分段三次多项式的缺点
- 三次只能保证段和段之间的一阶和二阶之间的连续性;
- 对于非常密集的点,为了准确通过这些点可能导致的过拟合(也就是龙格现象);
- 是一种局部插补的方法,可能从整体上看不是我们想要的趋势;
可以想象,如果你有一条曲线,通过几个数据点在这条曲线附近取样,然后尝试用一段段的三次多项式将这些点连起来。如果这些点非常密集且分布不均匀,插值曲线可能会在不同点之间“剧烈地”上下摆动以适应所有点,而这种摆动可能并不代表数据的真实趋势,而只是为了准确地通过所有给定的点。这就是过拟合的核心问题——在追求高精度拟合时,失去了对整体趋势的把握。
[1]李庆扬 王能超.数值分析(第4版)[M].华中科技大学出版社(原华中理工出版社)出版社,2006.
[2]金凯德,WardCheney,金凯德,等.数值分析[M].机械工业出版社,2005.
原文地址:https://blog.csdn.net/weixin_39258979/article/details/143014207
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!