关于SciPy UnivariateSpline平滑条件第二个公式正确性的疑问
我最近在捣鼓SciPy的UnivariateSpline工具,发现文档里给出的第二个平滑条件公式好像有问题——作为一名搞数学的,我一眼就觉得这事儿不对头,来跟大家聊聊我的看法。
先说说第一个公式,这个我觉得是完全合理的:
文档里给出的第一个停止添加节点的平滑条件是:
sum((w[i] * (y[i]-spl(x[i])))**2, axis=0) <= s
简单说就是加权残差平方和(我把它定义为error)不超过平滑参数s,逻辑上说得通,哪怕语法不是标准Python(用了索引i和NumPy风格的求和写法)也没关系,核心意思是对的。
但文档接着说,因为数值计算的问题,实际用的是另一个条件,也就是这个第二个公式:
abs(sum((w[i] * (y[i]-spl(x[i])))**2, axis=0) - s) < 0.001 * s
代入error的话,这个式子就变成了abs(error - s) < 0.001 * s,数学上等价于:
0.999 * s < error < 1.001 * s
这就完全离谱了啊!第一个公式明明要求误差不超过s,第二个公式却要求误差必须卡在s的0.999到1.001倍之间——这相当于强制误差几乎等于s,和第一个公式的核心逻辑完全矛盾。
为了验证这个问题,我写了一段测试代码:
from scipy.interpolate import UnivariateSpline x = range(4) y = [0.01, 1.01, 2.01, 3.01] spl = UnivariateSpline(x, y, s=1) print(spl(x)) print(sum((yi-spl(xi))**2 for xi, yi in zip(x,y))) # 输出结果: #[0.01 1.01 2.01 3.01] #3.6412113011071178e-34
你看,这里计算出的error几乎是0,远小于0.999*s(也就是0.999),完全不符合文档里第二个公式的要求,但代码却正常运行得到了完美插值的样条。这说明实际的停止条件肯定不是文档里写的这个第二个公式。
我个人觉得,文档里的第二个公式明显写错了——应该去掉绝对值,改成error < 1.001 * s,这样既给数值计算留了0.1%的余量,又符合第一个公式的核心逻辑:误差不超过s的1.001倍。
我还去翻了UnivariateSpline的源码,发现核心算法是调用dfitpack.fpcurf0这个底层函数,但这个函数的源码我没找到,没法确认实际的停止条件到底是什么。
我是不是漏看了什么细节?还是说文档里的第二个公式确实是错误的?
备注:内容来源于stack exchange,提问作者Andrew Kelley

