大数组的插值与外推

4 投票
2 回答
1295 浏览
提问于 2025-04-18 07:48

我有一个很大的数组 y,这个数组是在一个不均匀的、有序的网格 x 上定义的。这个数组的长度通常在 N~2^14 到 N~2^18 之间。我想对这个数组进行样条插值(或者说二次插值)。我遇到的问题是,即使在较小的 N 值下,插值的计算也非常耗时。

import numpy as np
from scipy.interpolate import interp1d
N = 2 ** 12 # = 4096
x = np.linspace(0, 2*np.pi, N)
y = np.sin(x)
%time f = interp1d(x, y, 'cubic', )

CPU times: user 8min 5s, sys: 1.39 s, total: 8min 7s
Wall time: 8min 7s

我看到的一个选择是,我只需要在非常有限的数据点上获取插值的值。有没有办法只在需要的时候计算插值呢?

你能建议一个替代方案吗?这个方案还需要能够在 x.min() 下面和 x.max() 以上的值进行外推。

谢谢!

2 个回答

4

为了补充@HYRY的评论,并支持他建议使用 InterpolatedUnivariateSpline 的观点,我做了一些不同数组长度的基准测试。

从下面的结果来看,interp1d 的表现似乎不太理想(纵轴是每个点的计算时间的对数,越负越快,横轴是 N 的2的幂)。

即使在 interp1d 表现最好的地方(大约在 N=2**42**5),InterpolatedUnivariateSpline 的速度也快了大约2.5个数量级。下面的代码可以用来绘制这些图。

interp1d plot

InterpolatedUnivariateSpline

import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d,InterpolatedUnivariateSpline

t=[]

for i in range(2,24):
  N = 2 ** i
  x = np.linspace(0, 2*np.pi, N)
  y = np.sin(x)
  t_=time.time()
  for j in range(20):#to make results more robust
    #f=interp1d(x,y,kind=3)
    f = InterpolatedUnivariateSpline(x, y, k=3)
  t_=time.time()-t_
  t.append(np.log(t_/N))
plt.plot(np.arange(22)+2,t)

需要注意的是,InterpolatedUnivariateSpline 在处理大数组时会占用更多内存,所以如果内存使用是个问题的话,interp1d 可能是更好的选择。

4

如果你的数据分布不均匀,你可以考虑使用一种更通用的插值技术,比如B样条。

你可以把数据看作是一些系数和基础函数的总和(比如用不均匀选择的节点的B样条,或者是一个由高斯函数组成的径向基函数网络)。这些函数需要能够覆盖你感兴趣的区域。

接下来,你可以用最小二乘法来估算这些系数,然后在你需要的任何地方以所需的分辨率重新取样。如果你采用这种方法,你还可以根据平滑度来调整系统,以便在x.min()和x.max()之外得到更合理的值。

这就是所谓的配点法:假设你的样本值存储在向量x和y中。你可以把基础向量设置为phi_k(x)的取样版本。

然后建立基础B = c_[phi_1,phi_2,...,phi_M],并使用最小二乘法:c,res,rnk,sv = lstsq(B,y)。

如果基础多项式的数量比较少,这个过程会很快。

现在你的向量c里包含了系数。你可以通过在感兴趣的点构建新的基础向量来计算新值:Bnew = c_[phi_1_new,phi_2_new,...,phi_M_new]。

然后通过投影计算y_new = dot(Bnew,c)。

  • 这种方法让你可以轻松地添加任何你选择的正则化方式。
  • 并且可以在任意点重新取样。
  • 使用任何适合你问题的基础函数。
  • 如果M足够小,那么这个系统可以快速解决。

撰写回答