感谢大家的回复。以下是对它们的另一次总结尝试。如果我说了太多“显而易见”的事情,请原谅:我以前不知道最小二乘,所以一切都对我来说是新的。
多项式插值是拟合一个度数为n
的多项式,给定n+1
个数据点,例如找到一个立方体,正好通过四个给定点。如问题中所述,这不是我想要的——我有很多点,并且想要一个低阶多项式(除非我们很幸运,否则只会近似拟合),但由于一些答案坚持谈论它,我应该提一下:)拉格朗日插值多项式, 范德蒙矩阵等。
对于单变量情况(只有一个变量x,fj是单项式xj),可以使用Numpy中的polyfit
函数:
>>> import numpy
>>> xs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> ys = [1.1, 3.9, 11.2, 21.5, 34.8, 51, 70.2, 92.3, 117.4, 145.5]
>>> p = numpy.poly1d(numpy.polyfit(xs, ys, deg=2))
>>> print p
2
1.517 x + 2.483 x + 0.4927
对于多元情况或线性最小二乘问题,可以使用SciPy。如其文档所述, 它接受一个值为 fj(xi) 的矩阵 A。(理论上它找到了A的Moore-Penrose伪逆)。在我们上面的例子中,涉及到 (xi,yi,Zi),拟合一个多项式意味着fj是x()y()的单项式。以下代码将找到最佳二次多项式(如果你更改“degree = 2”行,则会得到任何其他次数的最佳多项式):
from scipy import linalg
import random
n = 20
x = [100*random.random() for i in range(n)]
y = [100*random.random() for i in range(n)]
Z = [(x[i]+y[i])**2 + 0.01*random.random() for i in range(n)]
degree = 2
A = []
for i in range(n):
A.append([])
for xd in range(degree+1):
for yd in range(degree+1-xd):
A[i].append((x[i]**xd)*(y[i]**yd)) #f_j(x_i)
c,_,_,_ = linalg.lstsq(A,Z)
j = 0
for xd in range(0,degree+1):
for yd in range(0,degree+1-xd):
print " + (%.2f)x^%dy^%d" % (c[j], xd, yd),
j += 1
打印
+ (0.01)x^0y^0 + (-0.00)x^0y^1 + (1.00)x^0y^2 + (-0.00)x^1y^0 + (2.00)x^1y^1 + (1.00)x^2y^0
因此,它发现该多项式为x2+2xy+y2+0.01。[最后一项有时是-0.01,有时是0,这是由于我们添加的随机噪声所导致的。]
Python+Numpy/Scipy的替代方案包括R和计算机代数系统:Sage、Mathematica、Matlab、Maple。甚至Excel也可能能够完成。Numerical Recipes讨论了自己实现方法的方法(使用C、Fortran)。
x=y=range(20)
代替随机点时,它总是产生1.33x2+1.33xy+1.33y2,这很令人困惑... 直到我意识到因为我总是有x[i]=y[i]
,多项式是相同的:x2+2xy+y2 = 4x2 = (4/3)(x2+xy+y2)。所以教训是选择点很重要,以获得“正确”的多项式。(如果可以选择,应该选择Chebyshev nodes进行多项式插值; 不确定在最小二乘法中是否也是如此。)degree
改为3、4或5,它仍然大多识别相同的二次多项式(更高次数的系数为0),但对于更大的次数,它开始拟合更高次数的多项式。但即使是6次,取更多的n(比如200个数据点而不是20个)仍然适合二次多项式。所以教训是要避免过拟合,这可能有助于尽可能多地采集数据点。是的,通常使用最小二乘法来实现这个目标。还有其他指定多项式拟合程度的方法,但最小二乘法的理论最简单。一般的理论被称为线性回归。
你最好从Numerical Recipes开始学起。
R是免费的,可以完成你想要的所有任务,但它有一个很大的学习曲线。
如果你有Mathematica的访问权限,可以使用Fit函数进行最小二乘拟合。我想Matlab及其开源对应物Octave也有类似的功能。
对于(x,f(x))的情况:
import numpy
x = numpy.arange(10)
y = x**2
coeffs = numpy.polyfit(x, y, deg=2)
poly = numpy.poly1d(coeffs)
print poly
yp = numpy.polyval(poly, x)
print (yp-y)
def Tn(n, x):
if n==0:
return 1.0
elif n==1:
return float(x)
else:
return (2.0 * x * Tn(n - 1, x)) - Tn(n - 2, x)
class ChebyshevFit:
def __init__(self):
self.Tn = Memoize(Tn)
def fit(self, data, degree=None):
"""fit the data by a 'minimal squares' linear combination of chebyshev polinomials.
cfr: Conte, de Boor; elementary numerical analysis; Mc Grow Hill (6.2: Data Fitting)
"""
if degree is None:
degree = 5
data = sorted(data)
self.range = start, end = (min(data)[0], max(data)[0])
self.halfwidth = (end - start) / 2.0
vec_x = [(x - start - self.halfwidth)/self.halfwidth for (x, y) in data]
vec_f = [y for (x, y) in data]
mat_phi = [numpy.array([self.Tn(i, x) for x in vec_x]) for i in range(degree+1)]
mat_A = numpy.inner(mat_phi, mat_phi)
vec_b = numpy.inner(vec_f, mat_phi)
self.coefficients = numpy.linalg.solve(mat_A, vec_b)
self.degree = degree
def evaluate(self, x):
"""use Clenshaw algorithm
http://en.wikipedia.org/wiki/Clenshaw_algorithm
"""
x = (x-self.range[0]-self.halfwidth) / self.halfwidth
b_2 = float(self.coefficients[self.degree])
b_1 = 2 * x * b_2 + float(self.coefficients[self.degree - 1])
for i in range(2, self.degree):
b_1, b_2 = 2.0 * x * b_1 + self.coefficients[self.degree - i] - b_2, b_1
else:
b_0 = x*b_1 + self.coefficients[0] - b_2
return b_0
记住,近似多项式和找到一个精确的多项式之间有很大的区别。
例如,如果我给你4个点,你可以:
一定要选择适合你的方法!
如果你知道如何将最小二乘问题表示为线性代数问题,那么使用 Excel 的矩阵函数快速得出一个适配方案是相当简单的。(这取决于你认为 Excel 作为线性代数求解器的可靠程度。)
拉格朗日插值多项式在某种意义上是适合给定数据点的“最简单”的插值多项式。
有时它会出现问题,因为它可能在数据点之间变化很大。