我想弄清楚我不明白的是什么。
我正在关注 http://www.scipy.org/Cookbook/FittingData 并尝试拟合正弦波。真正的问题是卫星磁力计数据,它在旋转的航天器上产生漂亮的正弦波。我创建了一个数据集,然后尝试调整它以恢复输入。
这是我的代码:
import numpy as np
from scipy import optimize
from scipy.optimize import curve_fit, leastsq
import matplotlib.pyplot as plt
class Parameter:
def __init__(self, value):
self.value = value
def set(self, value):
self.value = value
def __call__(self):
return self.value
def fit(function, parameters, y, x = None):
def f(params):
i = 0
for p in parameters:
p.set(params[i])
i += 1
return y - function(x)
if x is None: x = np.arange(y.shape[0])
p = [param() for param in parameters]
return optimize.leastsq(f, p, full_output=True, ftol=1e-6, xtol=1e-6)
# generate a perfect data set (my real data have tiny error)
def mysine(x, a1, a2, a3):
return a1 * np.sin(a2 * x + a3)
xReal = np.arange(500)/10.
a1 = 200.
a2 = 2*np.pi/10.5 # omega, 10.5 is the period
a3 = np.deg2rad(10.) # 10 degree phase offset
yReal = mysine(xReal, a1, a2, a3)
# plot the real data
plt.figure(figsize=(15,5))
plt.plot(xReal, yReal, 'r', label='Real Values')
# giving initial parameters
amplitude = Parameter(175.)
frequency = Parameter(2*np.pi/8.)
phase = Parameter(0.0)
# define your function:
def f(x): return amplitude() * np.sin(frequency() * x + phase())
# fit! (given that data is an array with the data to fit)
out = fit(f, [amplitude, frequency, phase], yReal, xReal)
period = 2*np.pi/frequency()
print amplitude(), period, np.rad2deg(phase())
xx = np.linspace(0, np.max(xReal), 50)
plt.plot( xx, f(xx) , label='fit')
plt.legend(shadow=True, fancybox=True)
这使得这个情节:
恢复的
[44.2434221897 8.094832581 -61.6204033699]
拟合参数与我开始时没有相似之处。
对我不理解或做错的事情有什么想法吗?
scipy.__version__
'0.10.1'
编辑: 建议修复一个参数。在上面的示例中,将幅度固定为
np.histogram(yReal)[1][-1]
仍然会产生不可接受的输出。适合:[175.0 8.31681375217 6.0]
我应该尝试不同的适合方法吗?有哪些建议?
这里是一些实现了zhenya 的一些想法的代码。 它使用
yhat = fftpack.rfft(yReal)
idx = (yhat**2).argmax()
freqs = fftpack.rfftfreq(N, d = (xReal[1]-xReal[0])/(2*pi))
frequency = freqs[idx]
猜测数据的主要频率,并且
amplitude = yReal.max()
猜测振幅。
import numpy as np
import scipy.optimize as optimize
import scipy.fftpack as fftpack
import matplotlib.pyplot as plt
pi = np.pi
plt.figure(figsize = (15, 5))
# generate a perfect data set (my real data have tiny error)
def mysine(x, a1, a2, a3):
return a1 * np.sin(a2 * x + a3)
N = 5000
xmax = 10
xReal = np.linspace(0, xmax, N)
a1 = 200.
a2 = 2*pi/10.5 # omega, 10.5 is the period
a3 = np.deg2rad(10.) # 10 degree phase offset
print(a1, a2, a3)
yReal = mysine(xReal, a1, a2, a3) + 0.2*np.random.normal(size=len(xReal))
yhat = fftpack.rfft(yReal)
idx = (yhat**2).argmax()
freqs = fftpack.rfftfreq(N, d = (xReal[1]-xReal[0])/(2*pi))
frequency = freqs[idx]
amplitude = yReal.max()
guess = [amplitude, frequency, 0.]
print(guess)
(amplitude, frequency, phase), pcov = optimize.curve_fit(
mysine, xReal, yReal, guess)
period = 2*pi/frequency
print(amplitude, frequency, phase)
xx = xReal
yy = mysine(xx, amplitude, frequency, phase)
# plot the real data
plt.plot(xReal, yReal, 'r', label = 'Real Values')
plt.plot(xx, yy , label = 'fit')
plt.legend(shadow = True, fancybox = True)
plt.show()
产量
(200.0, 0.5983986006837702, 0.17453292519943295) # (a1, a2, a3)
[199.61981404516041, 0.61575216010359946, 0.0] # guess
(200.06145097308041, 0.59841420869261097, 0.17487141943703263) # fitted parameters
请注意,通过使用 fft,频率的猜测已经非常接近最终拟合参数。
看来你不需要修复任何参数。 通过使频率猜测更接近实际值,
optimize.curve_fit
能够收敛到合理的答案。
从我玩了一下
leastsq
所看到的(没有食谱中的花哨内容,只是简单地直接调用 leastsq
--- 顺便说一句,full_output=True
是你的朋友),它是一次性拟合幅度、频率和相位这三项是非常困难的。另一方面,如果我固定幅度并适合频率和相位,它就可以工作;如果我固定频率并适合幅度和相位,它也可以工作。
这里有不止一种出路。最简单的可能是——如果您确定只有一个正弦波(这很容易通过傅里叶变换进行检查),那么您只需根据信号的连续最大值之间的距离就可以知道频率。然后拟合剩下的两个参数。
如果您拥有的是多个谐波的混合,那么傅里叶变换会再次告诉您这一点。
有一个名为 pyestimate 的 Python 库,它实现了正弦曲线拟合。它可以适应嘈杂的正弦波:
#!pip install pyestimate
from pyestimate import sin_param_estimate
import numpy as np
import matplotlib.pyplot as plt
# define a signal to be fitted
n = np.arange(100)
s = 1.234 * np.cos(2*np.pi*0.0345*n + np.pi/7)
# add some noise
x = s + np.random.normal(scale=0.5, size=len(n))
# fit amplitude, frequency and phase
A,f,phi = sin_param_estimate(x)
# plot result
plt.plot(s, '-', label='original sine')
plt.plot(x, '.', label='noisy input data')
plt.plot(A*np.cos(2*np.pi*f*n+phi), 'r--', label='fitted sine')
plt.legend()
pyestimate 还实现了正弦波总和的拟合。