当将正弦拟合为周期数据时,如何改善scipy.optimize.curve_fit参数的初始猜测,否则如何改善拟合?

时间:2019-02-01 21:20:22

标签: python scipy curve-fitting

我有一个包含度量的a_i, f, phi_n文件。第一列是测量时间,第二列是相应的测量值,第三列是误差。 The file can be found here.我想使用Python将函数g的参数import numpy as np data=np.genfromtxt('signal.data') time=data[:,0] signal=data[:,1] signalerror=data[:,2] 拟合到数据中:

enter image description here

读取以下数据:

import matplotlib.pyplot as plt
plt.figure()
plt.plot(time,signal)
plt.scatter(time,signal,s=5)
plt.show()

绘制数据:

from gatspy.periodic import LombScargleFast
dmag=0.000005
nyquist_factor=40

model = LombScargleFast().fit(time, signal, dmag)
periods, power = model.periodogram_auto(nyquist_factor)

model.optimizer.period_range=(0.2, 10)
period = model.best_period

获得结果:

enter image description here

现在让我们计算周期信号的初步频率猜测:

N=10

我们得到的结果是:0.5467448186001437

我为def G(x, A_0, A_1, phi_1, A_2, phi_2, A_3, phi_3, A_4, phi_4, A_5, phi_5, A_6, phi_6, A_7, phi_7, A_8, phi_8, A_9, phi_9, A_10, phi_10, freq): return (A_0 + A_1 * np.sin(2 * np.pi * 1 * freq * x + phi_1) + A_2 * np.sin(2 * np.pi * 2 * freq * x + phi_2) + A_3 * np.sin(2 * np.pi * 3 * freq * x + phi_3) + A_4 * np.sin(2 * np.pi * 4 * freq * x + phi_4) + A_5 * np.sin(2 * np.pi * 5 * freq * x + phi_5) + A_6 * np.sin(2 * np.pi * 6 * freq * x + phi_6) + A_7 * np.sin(2 * np.pi * 7 * freq * x + phi_7) + A_8 * np.sin(2 * np.pi * 8 * freq * x + phi_8) + A_9 * np.sin(2 * np.pi * 9 * freq * x + phi_9) + A_10 * np.sin(2 * np.pi * 10 * freq * x + phi_10)) 定义了适合以下功能的函数:

G

现在我们需要一个适合def fitter(time, signal, signalerror, LSPfreq): from scipy import optimize pfit, pcov = optimize.curve_fit(lambda x, _A_0, _A_1, _phi_1, _A_2, _phi_2, _A_3, _phi_3, _A_4, _phi_4, _A_5, _phi_5, _A_6, _phi_6, _A_7, _phi_7, _A_8, _phi_8, _A_9, _phi_9, _A_10, _phi_10, _freqfit: G(x, _A_0, _A_1, _phi_1, _A_2, _phi_2, _A_3, _phi_3, _A_4, _phi_4, _A_5, _phi_5, _A_6, _phi_6, _A_7, _phi_7, _A_8, _phi_8, _A_9, _phi_9, _A_10, _phi_10, _freqfit), time, signal, p0=[11, 2, 0, #p0 is the initial guess for numerical fitting 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, LSPfreq], sigma=signalerror, absolute_sigma=True) error = [] # DEFINE LIST TO CALC ERROR for i in range(len(pfit)): try: error.append(np.absolute(pcov[i][i]) ** 0.5) # CALCULATE SQUARE ROOT OF TRACE OF COVARIANCE MATRIX except: error.append(0.00) perr_curvefit = np.array(error) return pfit, perr_curvefit 的函数:

LSPfreq=1/period
pfit, perr_curvefit = fitter(time, signal, signalerror, LSPfreq)

plt.figure()
model=G(time,pfit[0],pfit[1],pfit[2],pfit[3],pfit[4],pfit[5],pfit[6],pfit[7],pfit[8],pfit[8],pfit[10],pfit[11],pfit[12],pfit[13],pfit[14],pfit[15],pfit[16],pfit[17],pfit[18],pfit[19],pfit[20],pfit[21])
plt.scatter(time,model,marker='+')
plt.plot(time,signal,c='r')
plt.show()

检查我们得到了什么

p0

屈服:

enter image description here

这显然是错误的。如果我在函数fitter的定义中使用最初的猜测p0=[11, 1, 0, 0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, LSPfreq] ,我可以获得更好的结果。设置

p0

给我们(放大):

enter image description here

哪个更好。尽管高频分量的振幅被猜测为零,但高频分量仍然存在。原始的p0似乎比基于视觉检查数据的修改版本更合理。

我为p0设置了不同的值,而更改p0的确改变了结果,但是我没有得到一条与数据完全吻合的线。

此模型拟合方法为什么会失败?我该如何改善身体状况?

The whole code can be found here.


此问题的原始版本发布于here


编辑:

PyCharm对代码的 ${__eval($.value.page[${index}].hash)} 部分发出警告:

enter image description here

  

预期类型为'Union [None,int,float,complex]',而改为'List [Union [int,float],Any]]'

我不知道该如何处理,但可能与此有关。

2 个答案:

答案 0 :(得分:3)

对于在嘈杂的数据上计算最适合的周期模型,典型的基于优化的方法通常会在除最人为的情况以外的所有情况下失败。这是因为代价函数在频率空间中将是高度多模态的,因此,任何缺少密集网格搜索的优化方法几乎肯定会陷入局部最小值。

在这种情况下,最佳密集网格搜索将是您用来查找初始值的Lomb-Scargle周期图的变体,并且您可以跳过优化步骤,因为Lomb-Scargle已经为您进行了优化。

Astropy中提供了最佳的广义Lomb-Scargle最佳Python实现(完整披露:我编写了大部分实现)。您上面使用的模型在这里称为截断傅立叶模型,可以通过为nterms参数指定适当的值来进行拟合。

使用数据,您可以首先拟合并绘制具有五个傅立叶项的广义周期图:

from astropy.stats import LombScargle
ls = LombScargle(time, signal, signalerror, nterms=5)
freq, power = ls.autopower()
plt.plot(freq, power);

enter image description here

混叠在这里很明显:由于数据点之间的间隔,所有高于24的频率只是频率低于24的信号的混叠。考虑到这一点,让我们重新计算一下信号的相关部分周期图:

freq, power = ls.autopower(maximum_frequency=24)
plt.plot(freq, power);

enter image description here

这向我们展示了在网格上的每个频率上,最佳拟合傅里叶模型的有效卡方平方。 现在,我们可以找到最佳频率并为该频率计算最佳拟合模型:

best_freq = freq[power.argmax()]
tfit = np.linspace(time.min(), time.max(), 10000)
signalfit = ls.model(tfit, best_freq)

plt.errorbar(time, signal, signalerror, fmt='.k', ecolor='lightgray');
plt.plot(tfit, signalfit)
plt.xlim(time[500], time[800]);

enter image description here

如果您对模型参数本身感兴趣,则可以使用lomb-scargle算法背后的低级例程。

from astropy.stats.lombscargle.implementations.mle import design_matrix
X = design_matrix(time, best_freq, signalerror, nterms=5)
parameters = np.linalg.solve(np.dot(X.T, X), np.dot(X.T, signal / signalerror))

print(parameters)
# [ 1.18351382e+01  2.24194359e-01  5.72266632e-02 -1.23807286e-01
#  1.25825666e-02  7.81944277e-02 -1.10571718e-02 -5.49132878e-02
#  9.51544241e-03  3.70385961e-02  9.36161528e-06]

这些是线性化模型的参数,即

signal = p_0 + sum_{n=1}^{5}[p_{2n - 1} sin(2\pi n f t) + p_{2n} cos(2\pi n f t)]

这些线性sin / cos振幅可以用三角函数转换回非线性振幅和相位。

我相信这将是将多阶傅立叶级数拟合到模型的最佳方法,因为它避免了对行为不佳的成本函数进行优化,并使用快速算法使基于网格的计算变得容易处理。

答案 1 :(得分:1)

这是似乎可以很好地拟合数据的代码。这使用scipy的差异进化(DE)遗传算法来估计curve_fit()的初始参数。为了加快遗传算法的速度,该代码将前500个数据点的数据子集用于初始参数估计。尽管结果看起来很不错,但此问题具有许多参数的复杂错误空间,遗传算法将花费一些时间(在我的史前笔记本电脑上将近15分钟)。您应该考虑在午餐时间或午夜使用完整的数据集进行测试,以验证拟合的参数是否有任何有用的改进。 DE的scipy实现使用Latin Hypercube算法来确保对参数空间进行彻底搜索,这需要在搜索范围内进行搜索-请检查示例的显示范围是否合理。

import numpy as np

from scipy.optimize import differential_evolution
import warnings

data=np.genfromtxt('signal.data')

time=data[:,0]
signal=data[:,1]
signalerror=data[:,2]

# value for reduced size data set used in initial parameter estimation
# to sllow the genetic algorithm to run faster than with all data
geneticAlgorithmSlice = 500

import matplotlib.pyplot as plt
plt.figure()
plt.plot(time,signal)
plt.scatter(time,signal,s=5)
plt.show()



from gatspy.periodic import LombScargleFast
dmag=0.000005
nyquist_factor=40

model = LombScargleFast().fit(time, signal, dmag)
periods, power = model.periodogram_auto(nyquist_factor)

model.optimizer.period_range=(0.2, 10)
period = model.best_period
LSPfreq=1/period


def G(x, A_0,
         A_1, phi_1,
         A_2, phi_2,
         A_3, phi_3,
         A_4, phi_4,
         A_5, phi_5,
         A_6, phi_6,
         A_7, phi_7,
         A_8, phi_8,
         A_9, phi_9,
         A_10, phi_10,
         freq):
    return (A_0 + A_1 * np.sin(2 * np.pi * 1 * freq * x + phi_1) +
                  A_2 * np.sin(2 * np.pi * 2 * freq * x + phi_2) +
                  A_3 * np.sin(2 * np.pi * 3 * freq * x + phi_3) +
                  A_4 * np.sin(2 * np.pi * 4 * freq * x + phi_4) +
                  A_5 * np.sin(2 * np.pi * 5 * freq * x + phi_5) +
                  A_6 * np.sin(2 * np.pi * 6 * freq * x + phi_6) +
                  A_7 * np.sin(2 * np.pi * 7 * freq * x + phi_7) +
                  A_8 * np.sin(2 * np.pi * 8 * freq * x + phi_8) +
                  A_9 * np.sin(2 * np.pi * 9 * freq * x + phi_9) +
                  A_10 * np.sin(2 * np.pi * 10 * freq * x + phi_10))



# function for genetic algorithm to minimize (sum of squared error)
def sumOfSquaredError(parameterTuple):
    warnings.filterwarnings("ignore") # do not print warnings by genetic algorithm
    val = G(time[:geneticAlgorithmSlice], *parameterTuple)
    return np.sum((signal[:geneticAlgorithmSlice] - val) ** 2.0)

def generate_Initial_Parameters():
    parameterBounds = []
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([-50.0, 50.0])
    parameterBounds.append([LSPfreq/2.0, LSPfreq*2.0])

    # "seed" the numpy random number generator for repeatable results
    result = differential_evolution(sumOfSquaredError, parameterBounds, seed=3)
    return result.x


print("Starting genetic algorithm...")
# by default, differential_evolution completes by calling curve_fit() using parameter bounds
geneticParameters = generate_Initial_Parameters()
print("Genetic algorithm completed")


def fitter(time, signal, signalerror, initialParameters):

    from scipy import optimize

    pfit, pcov = optimize.curve_fit(G, time, signal, p0=initialParameters,
                                    sigma=signalerror, absolute_sigma=True)

    error = []  # DEFINE LIST TO CALC ERROR
    for i in range(len(pfit)):
        try:
            error.append(np.absolute(pcov[i][i]) ** 0.5)  # CALCULATE SQUARE ROOT OF TRACE OF COVARIANCE MATRIX
        except:
            error.append(0.00)
    perr_curvefit = np.array(error)

    return pfit, perr_curvefit


pfit, perr_curvefit = fitter(time, signal, signalerror, geneticParameters)

plt.figure()
model=G(time,*pfit) 
plt.scatter(time,model,marker='+')
plt.plot(time,model)
plt.plot(time,signal,c='r')
plt.show()