为什么scipy.optimize.curve_fit不能拟合阻尼正弦曲线?

tgabmvqs  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(104)

用户Nick奥德尔给出了a great answer来解决curve_fit不能很好地拟合正常曲线的问题-事实证明,相对于频率的目标函数并不“好”,并且有很多局部最小值与全局最小值相距甚远。为了解决这个问题,你应该首先使用FFT来近似频率,然后将其作为你的初始猜测。这是可行的,因为它将优化器定位在全局最优值附近,所以它不会在其他地方“丢失”。
现在,我有另一个数据集,这次是一个阻尼正弦曲线。我试图使用相同的FFT技巧来获得一个良好的曲线拟合,但我得到的只是一条直线。
我的代码是在this pastebin中给出的(正如你所看到的,我发现optimize.basinhopping工作得很好,但我希望有一个更好的版本)。
没有长数据列表的代码:

ydata=[float(_) for _ in ydata.strip().splitlines()]
 
 
from scipy.optimize import curve_fit as cf
import numpy as np
from matplotlib import pyplot as plt
from scipy.optimize import basinhopping as bh
xdata=np.arange(0.05,500.01,0.05)
 
def damped_sin_fun(x,a,b,c,d,e):
    return a*(np.exp(-x/e))*np.sin(b*x+c)+d
 
fft = np.fft.fft(ydata - np.array(ydata).mean())
timestep = xdata[1] - xdata[0]
freq = np.fft.fftfreq(len(fft), d=timestep)
largest_component = np.abs(fft).argmax()
phase_guess = np.angle(fft[largest_component]) * freq[largest_component]
initial_frequency_guess = freq[largest_component] * 2 * np.pi
 
p_opt,p_cov=cf(damped_sin_fun,xdata,ydata, p0=[0.05, initial_frequency_guess, np.pi/2 ,np.mean(ydata),2.3])
 
print(initial_frequency_guess)
print(p_opt)
#print(((damped_sin_fun(xdata,*p_opt)-ydata)**2).mean())
optimize=bh(lambda args: ((damped_sin_fun(xdata,*args)-ydata)**2).mean(), [0.05, initial_frequency_guess, np.pi/2 ,np.mean(ydata),2.3]).x
 
 
plt.plot(xdata,damped_sin_fun(xdata,*p_opt))
plt.xlim((0,10))
#plt.plot(xdata,damped_sin_fun(xdata,*optimize))
#plt.plot(xdata,ydata, 'r.-', ms=1)
plt.show()

字符串

oprakyz7

oprakyz71#

我会通过将Y分解为三个信号来解决这个问题:指数项,常数项和正弦项。

y = exp * sine + constant

字符串
然后,我将分别适合每三个之前组合它们。
为了拟合指数项,你不能只将它拟合到信号上,因为你还不知道正弦参数应该是什么。但是,你可以做的是选择一个指数项,它使信号的标准差沿着信号是恒定的。一旦信号沿着其长度具有恒定的幅度,就可以使用上一个问题中描述的过程来拟合正弦波。
在开始编写代码之前,我想特别注意这一部分:

exp_trend_data = pd.Series(ydata).rolling(window=samples_per_window).std().bfill().values


说明:不可能找到每个点的标准差,我做的是在一个滚动窗口中计算标准差,窗口大小为100。(这是以样本为单位,而不是时间为单位。)对于在100个样本之前的点,它使用向后填充。这是一个有点随意的参数,但我发现samples_per_window的值在20到1000之间工作得相当好。
拟合指数项的完整代码:

import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
from matplotlib import pyplot as plt

xdata = np.arange(0.05,500.01,0.05)
ydata = np.loadtxt('test579_data.txt')

samples_per_window = 100
exp_trend_data = pd.Series(ydata).rolling(window=samples_per_window).std().bfill().values

def exp_term(x, amplitude1, rate):
    return amplitude1 * np.exp(-x / rate)

beginning_value = exp_trend_data[:100].mean()
end_value = exp_trend_data[-100:].mean()
decrease_ratio = beginning_value / end_value
p0 = [beginning_value, np.max(xdata) / np.log(decrease_ratio)]
p_opt_exp, _ = curve_fit(exp_term, xdata, exp_trend_data, p0=p0)

plt.plot(exp_term(xdata, *p_opt_exp))
plt.plot(exp_trend_data)
plt.yscale('log')


下面是指数项(蓝色)与目标项(橙子)的关系图。


的数据
(Note:这里的Y轴是对数的。在半对数图上,任何指数项都是直线。然而,目标不是直的。这意味着这个问题中的衰减不是严格的指数衰减。)
接下来,拟合常数项。

constant_term = ydata.mean()


接下来,拟合正弦项。通过重新排列方程y = exp * sine + constant,我们可以得到正弦项本身。

sine_term_data = (ydata - constant_term) / exp_term(xdata, *p_opt_exp)
plt.plot(sine_term_data)


剧情:


的数据
这并没有完全消除标准差的变化,但已经足够接近了。
接下来,拟合正弦项。

def find_dominant_frequency(xdata, ydata):
    """Find dominiant frequency in units of radians"""
    fft = np.fft.fft(ydata - np.array(ydata).mean())
    timestep = xdata[1] - xdata[0]
    freq = np.fft.fftfreq(len(fft), d=timestep)
    fft = fft[:len(fft) // 2]
    freq = freq[:len(freq) // 2]
    largest_component = np.abs(fft).argmax()
    phase_guess = np.angle(fft[largest_component]) * freq[largest_component]
    initial_frequency_guess = freq[largest_component] * 2 * np.pi
    return initial_frequency_guess, phase_guess

def sine_term(x, amplitude2, freq, phase):
    return amplitude2*np.sin(freq*x+phase)

initial_frequency_guess, phase_guess = find_dominant_frequency(xdata, sine_term_data)
p0 = [np.sqrt(2), initial_frequency_guess, phase_guess]
p_opt_sine, _ = curve_fit(sine_term, xdata, sine_term_data, p0=p0)
plt.plot(xdata, sine_term_data)
plt.plot(xdata, sine_term(xdata, *p_opt_sine))
pos = 300
plt.xlim(pos, pos+20)


接下来,所有的术语都可以组合在一起。

def damped_sin_fun(x, amplitude, freq, phase, intercept, rate):
    return amplitude * np.exp(-x / rate) * np.sin(freq * x + phase) + intercept

amplitude1, rate = p_opt_exp
amplitude2, freq_guess, phase_guess = p_opt_sine
p0 = [
    amplitude1 * amplitude2,
    freq_guess,
    phase_guess,
    constant_term,
    rate,
]
plt.plot(xdata, damped_sin_fun(xdata, *p0))
plt.plot(xdata, ydata)


你可以运行另一个曲线拟合来同时调整所有参数。这与独立解的结果非常相似,但略有不同。

p_opt, _ = curve_fit(damped_sin_fun, xdata, ydata, p0=p0)
print(p0)
print(p_opt.tolist())
plt.plot(xdata, ydata)
plt.plot(xdata, damped_sin_fun(xdata, *p_opt))
pos = 300
plt.xlim(pos, pos+20)


这让你很不爽

相关问题