Suggestions for fitting noisy exponentials with scipy curve_fit?

荒凉一梦 提交于 2019-12-04 10:58:51

I tried fitting your linked data to your posted equation using a genetic algorithm for initial parameter estimation, with results similar to yours.

If you might use another equation, I found that the Weibull Peak equation (with an offset) gave an OK-looking fit as shown on the attached graph

y = a * exp(-0.5 * (ln(x/b)/c)2) + Offset

Fitting target of lowest sum of squared absolute error = 9.4629510487855703E+04

a = -8.0940765409447977E+01
b =  1.3557513687506761E-01
c = -4.3577079449636000E-02
Offset = -6.9918802683084749E+01

Degrees of freedom (error): 997
Degrees of freedom (regression): 3
Chi-squared: 94629.5104879
R-squared: 0.851488191713
R-squared adjusted: 0.85104131566
Model F-statistic: 1905.42363136
Model F-statistic p-value: 1.11022302463e-16
Model log-likelihood: -3697.11689531
AIC: 7.39483895167
BIC: 7.41445435538
Root Mean Squared Error (RMSE): 9.72290982743

a = -8.0940765409447977E+01
       std err: 1.42793E+00
       t-stat: -6.77351E+01
       95% confidence intervals: [-8.32857E+01, -7.85958E+01]

b = 1.3557513687506761E-01
       std err: 9.67181E-09
       t-stat: 1.37856E+03
       95% confidence intervals: [1.35382E-01, 1.35768E-01]

c = -4.3577079449636000E-02
       std err: 6.05635E-07
       t-stat: -5.59954E+01
       95% confidence intervals: [-4.51042E-02, -4.20499E-02]

Offset = -6.9918802683084749E+01
       std err: 1.38358E-01
       t-stat: -1.87972E+02
       95% confidence intervals: [-7.06487E+01, -6.91889E+01]

Coefficient Covariance Matrix
[  1.50444441e-02   3.31862722e-11  -4.34923071e-06  -1.02929117e-03]
[  3.31862722e-11   1.01900512e-10   3.26959463e-11  -6.22895315e-12]
[ -4.34923071e-06   3.26959463e-11   6.38086601e-09  -1.11146637e-06]
[ -1.02929117e-03  -6.22895315e-12  -1.11146637e-06   1.45771350e-03]

I come from an EE background, looked for "System Identification" tools, but didn't find what I expected in the Python libs I found

so I worked out a "naive" SysID solution in the frequency domain which I am more familiar with

I removed the initial offset, assumed a step excitation, doubled, inverted the data set to be periodic for the fft processing steps

after fitting to a Laplace/frequency domain transfer function with scipy.optimize.least_squares:

def tf_model(w, td0,ta,tb,tc): # frequency domain transfer function w delay
    return np.exp(-1j*w/td0)*(1j*w*ta)/(1j*w*tb + 1)/(1j*w*tc + 1)

I converted back into a time domain step response with a little help from sympy

inverse_laplace_transform(s*a/((s*b + 1)*(s*c + 1)*s), s, t

after a little simplification:

def tdm(t, a, b, c):
    return -a*(np.exp(-t/c) - np.exp(-t/b))/(b - c)

applied a normalization to the frequency domain fitted constants, lined up the plots

import numpy as np
from matplotlib import pyplot as plt
from scipy.optimize import least_squares

data = np.loadtxt(open("D:\Downloads\\transient_data.csv","rb"),
                  delimiter=",", skiprows=1)

x, y = zip(*data[1:]) # unpacking, dropping one point to get 1000 
x, y = np.array(x), np.array(y)

y = y - np.mean(y[:20]) # remove linear baseline from starting data estimate

xstep = np.sign((x - .12))*-50 # eyeball estimate step start time, amplitude

x = np.concatenate((x,x + x[-1]-x[0])) # extend, invert for a periodic data set
y = np.concatenate((y, -y))
xstep = np.concatenate((xstep, -xstep))

# frequency domain transforms of the data, assumed square wave stimulus
fy = np.fft.rfft(y)
fsq = np.fft.rfft(xstep)

# only keep 1st ~50 components of the square wave
# this is equivalent to applying a rectangular window low pass
K = np.arange(1,100,2) # 1st 50 nonzero fft frequency bins of the square wave
# form the frequency domain transfer function from fft data: Gd
Gd = fy[1:100:2]/fsq[1:100:2]

def tf_model(w, td0,ta,tb,tc): # frequency domain transfer function w delay
    return np.exp(-1j*w/td0)*(1j*w*ta)/(1j*w*tb + 1)/(1j*w*tc + 1)

td0,ta,tb,tc = 0.1, -1, 0.1, 0.01

x_guess = [td0,ta,tb,tc]

# cost function, "residual" with weighting by stimulus frequency components**2?
def func(x, Gd, K):
    return (np.conj(Gd - tf_model(K, *x))*
                   (Gd - tf_model(K, *x))).real/K #/K # weighting by K powers

res = least_squares(func, x_guess, args=(Gd, K),
                    bounds=([0.0, -100, 0, 0],
                            [1.0, 0.0, 10, 1]),
                             max_nfev=100000, verbose=1)

td0,ta,tb,tc = res['x']

# convolve model w square wave in frequency domain
fy = fsq * tf_model(np.arange(len(fsq)), td0,ta,tb,tc)

ym = np.fft.irfft(fy) # back to time domain 

print(res)

plt.plot(x, xstep, 'r')
plt.plot(x, y, 'g')
plt.plot(x, ym, 'k')

# finally show time domain step response function, normaliztion
def tdm(t, a, b, c):
    return -a*(np.exp(-t/c) - np.exp(-t/b))/(b - c)

# normalizing factor for frequency domain, dataset time range
tn = 2*np.pi/(x[-1]-x[0])
ta, tb, tc = ta/tn, tb/tn, tc/tn

y_tdm = tdm(x - 0.1, ta, tb, tc)

# roll shifts yellow y_tdm to (almost) match black frequency domain model
plt.plot(x, 100*np.roll(y_tdm, 250), 'y')

green: doubled, inverted data to be periodic
red: guestimated starting step, also doubled, inverted to periodic square wave
black: frequency domain fitted model convolved with square wave
yellow: fitted frequency domain model translated back into a time domain step response, rolled to compare

     message: '`ftol` termination condition is satisfied.'
        nfev: 40
        njev: 36
  optimality: 0.0001517727368912258
      status: 2
     success: True
           x: array([ 0.10390021, -0.4761587 ,  0.21707827,  0.21714922])
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!